#!/usr/bin/env python3

# Copyright 2024 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""
Example script to populate a debusine instance with example data

This is useful to get a full example local database to use for UI development
"""


import contextlib
import datetime
import logging
import os
import shlex
import shutil
import subprocess
import tempfile
from functools import cached_property
from pathlib import Path
from typing import Any

import yaml

import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'debusine.project.settings')
django.setup()
from django.core.management import execute_from_command_line
from django.db import transaction  # noqa: E402

from debusine.artifacts.models import ArtifactCategory, CollectionCategory
from debusine.db.models import (
    Artifact,
    ArtifactRelation,
    Collection,
    CollectionItem,
    File,
    FileInArtifact,
    User,
    WorkRequest,
    Workspace,
)  # noqa: E402
from debusine.server.file_backend.interface import (
    FileBackendInterface,
)  # noqa: E402
from debusine.db.playground import Playground


def debusine(*args: str, **kwargs: Any) -> subprocess.CompletedProcess:
    """Run debusine client."""
    kwargs.setdefault("check", True)

    cmdline = ["python3", "-m", "debusine.client"]
    cmdline.extend(args)

    logging.info("%s", shlex.join(cmdline))
    return subprocess.run(cmdline, **kwargs)


def debusine_admin(
    *args: str, input_data: dict[str, Any] | None = None, **kwargs: Any
) -> subprocess.CompletedProcess:
    """Run debusine-admin."""
    kwargs.setdefault("check", True)

    # If debusine-admin is not installed, fall back to ./manage.py.
    # This should allow running the playground from a git checkout, while still
    # working as an example script with debusine installed
    if (debusine_admin := shutil.which("debusine-admin")) is None:
        import debusine

        debusine_admin = str(
            Path(debusine.__file__).parent.parent / "manage.py"
        )

    cmdline = [debusine_admin]
    cmdline.extend(args)

    with contextlib.ExitStack() as stack:
        if input_data is not None:
            tmpfile = stack.enter_context(tempfile.TemporaryFile(mode="w+t"))
            yaml.safe_dump(input_data, stream=tmpfile)
            tmpfile.seek(0)
            kwargs["stdin"] = tmpfile

        logging.info("%s", shlex.join(cmdline))
        return subprocess.run(cmdline, **kwargs)


class Populate(Playground):
    """Populate a database with simulated activity."""

    def __init__(self):
        """Initialize playground generator."""
        super().__init__(
            default_user_password="playground",
            default_workspace_name="playground",
        )
        self.workspace.public = True
        self.workspace.save()

    @cached_property
    def workspace(self) -> Workspace:
        """Return the Playground workspace."""
        return self.get_default_workspace()

    @cached_property
    def user(self) -> User:
        """Return the Playground user."""
        return self.get_default_user()

    @cached_property
    def suite_collection(self) -> Collection:
        return self.create_collection(
            workspace=self.workspace,
            name="play_bookworm",
            category=CollectionCategory.SUITE,
            data={
                "may_reuse_versions": False,
                "release_fields": {
                    "Suite": "stable",
                    "Codename": "bookworm",
                    "Architectures": "all amd64 arm64 armel armhf i386"
                    " mips64el mipsel ppc64el s390x",
                    "Components": "main contrib non-free-firmware non-free",
                },
            },
        )

    @cached_property
    def archive_collection(self) -> Collection:
        return self.create_collection(
            name="play_debian",
            category="debian:archive",
            workspace=self.workspace,
            data={"may_reuse_versions": False},
        )

    def add_results_to_suite(self, work_request: WorkRequest) -> None:
        """Add the work request build results to suite_collection."""
        source = Artifact.objects.get(
            pk=work_request.task_data["input"]["source_artifact"]
        )

        suite_manager = self.suite_collection.manager

        if not CollectionItem.active_objects.filter(
            parent_collection=self.suite_collection, artifact=source
        ).exists():
            suite_manager.add_source_package(
                source, user=self.user, component="main", section="devel"
            )
        for binary in Artifact.objects.filter(
            created_by_work_request=work_request,
            category=ArtifactCategory.BINARY_PACKAGE,
        ):
            suite_manager.add_binary_package(
                binary,
                user=self.user,
                component="main",
                section="devel",
                priority="optional",
            )

    def populate_suite(self):
        """Populate the debian:suite collection with artifacts."""
        hello = self.create_source_artifact(name="hello", version="1.0-1")
        wr = self.simulate_package_build(hello, architecture="amd64")
        self.add_results_to_suite(wr)

        dpkg = self.create_source_artifact(name="dpkg", version="1.21.22")
        wr = self.simulate_package_build(dpkg, architecture="amd64")
        self.add_results_to_suite(wr)
        wr = self.simulate_package_build(dpkg, architecture="armhf")
        self.add_results_to_suite(wr)

    def populate_users(self):
        """Set password for test user."""
        self.user.set_password("playground")
        self.user.save()

    def populate_work_requests(self) -> None:
        """Simulate some work requests."""

        kwargs = {
            "workspace": self.workspace,
            "task_name": "noop",
            "task_data": {},
            "created_by": self.user,
        }

        WorkRequest.objects.create(
            status=WorkRequest.Statuses.COMPLETED,
            result=WorkRequest.Results.SUCCESS,
            **kwargs,
        )
        WorkRequest.objects.create(
            status=WorkRequest.Statuses.COMPLETED,
            result=WorkRequest.Results.FAILURE,
            **kwargs,
        )
        WorkRequest.objects.create(
            status=WorkRequest.Statuses.COMPLETED,
            result=WorkRequest.Results.ERROR,
            **kwargs,
        )
        WorkRequest.objects.create(
            status=WorkRequest.Statuses.ABORTED, **kwargs
        )


def main():
    logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")

    # Remove possible data from an old playground
    debusine_admin("delete_workspace", "Playground", "--yes", "--force")

    playground = Populate()

    # TODO: Add bookworm suite to debian archive (see #388)
    # with transaction.atomic():
    #     archive_manager = playground.archive_collection.manager()
    #     archive_manager.add_collection(
    #         playground.suite_collection, user=playground.user
    #     )

    # Add packages to the debian suite
    with transaction.atomic():
        playground.populate_suite()
        playground.populate_users()
        playground.populate_work_requests()


if __name__ == '__main__':
    main()
