# Copyright 2023 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.
"""Helper mixin for the integration tests."""
import subprocess
import tempfile
from pathlib import Path
from typing import Any

import requests

from utils.client import Client

import yaml

from debusine.artifacts.local_artifact import BinaryPackage, SourcePackage
from debusine.artifacts.models import ArtifactCategory
from debusine.test import TestHelpersMixin


class IntegrationTestHelpersMixin(TestHelpersMixin):
    """Utility methods for the integration tests."""

    @staticmethod
    def _create_artifact(
        artifact_type: ArtifactCategory,
        data: dict[str, Any],
        files: list[Path],
        base_directory: Path,
    ) -> int:
        """
        Create an artifact.

        :param artifact_type: type of artifact (e.g. debian:source-package,
          debian:binary-packages).
        :param data: type-specific data to attach to the artifact.
        :param files: list of paths to files to attach to the artifact.
        :param base_directory: base directory for files to upload (to allow
          calculating relative paths)
        """
        with tempfile.TemporaryDirectory(
            prefix="debusine-integration-tests-"
        ) as temp_directory:
            data_yaml = Path(temp_directory) / "artifact-data.yaml"
            data_yaml.write_text(yaml.safe_dump(data))

            return Client.execute_command(
                "create-artifact",
                artifact_type,
                "--workspace",
                "System",
                "--data",
                data_yaml,
                "--upload-base-directory",
                base_directory,
                "--upload",
                *files,
            )["artifact_id"]

    @classmethod
    def _create_apt_artifact(
        cls,
        package_name: str,
        apt_get_command: str,
        artifact_type: ArtifactCategory,
    ) -> int:
        """
        Download files using apt and create an artifact from them.

        :param package_name: package contained in the artifact.
        :param apt_get_command: e.g. "source", "download".
        :param artifact_type: type of artifact (e.g. debian:source-package,
          debian:binary-packages).
        """
        with tempfile.TemporaryDirectory(
            prefix="debusine-integration-tests-"
        ) as temp_directory:
            temp_path = Path(temp_directory)

            subprocess.check_call(
                ["apt-get", apt_get_command, "--download-only", package_name],
                cwd=temp_path,
            )

            files = list(temp_path.iterdir())

            if artifact_type == ArtifactCategory.SOURCE_PACKAGE:
                [dsc_file] = [
                    file for file in files if file.name.endswith(".dsc")
                ]
                version = dsc_file.name.split("_")[1]
                src_artifact = SourcePackage.create(
                    name=package_name, version=version, files=files
                )
                data = src_artifact.data.dict()
            elif artifact_type == ArtifactCategory.BINARY_PACKAGE:
                bin_artifact = BinaryPackage.create(file=files[0])
                data = bin_artifact.data.dict()
            else:
                raise NotImplementedError(
                    f"cannot generate artifact data for {artifact_type}"
                )

            return cls._create_artifact(artifact_type, data, files, temp_path)

    @classmethod
    def create_artifact_source(cls, package_name: str) -> int:
        """Create a source artifact with hello files."""
        return cls._create_apt_artifact(
            package_name, "source", ArtifactCategory.SOURCE_PACKAGE
        )

    @classmethod
    def create_artifact_binary(cls, package_name: str) -> int:
        """Create a binary artifact."""
        return cls._create_apt_artifact(
            package_name, "download", ArtifactCategory.BINARY_PACKAGES
        )

    @classmethod
    def create_artifact_upload(cls, package_names: list[str]) -> int:
        """Create an upload artifact containing some binary packages."""
        with tempfile.TemporaryDirectory(
            prefix="debusine-integration-tests-"
        ) as temp_directory:
            temp_path = Path(temp_directory)

            for package_name in package_names:
                subprocess.check_call(
                    ["apt-get", "download", "--download-only", package_name],
                    cwd=temp_path,
                )

            files = sorted(temp_path.iterdir())
            changes_file = temp_path / f"{package_names[0]}.changes"
            cls.write_changes_file(changes_file, files)

            return Client.execute_command(
                "import-debian-artifact",
                "--workspace",
                "System",
                changes_file,
            )["artifact_id"]

    @classmethod
    def create_artifact_build_logs(cls, filename: str, contents: str) -> int:
        """
        Create a build-log artifact.

        :param filename: filename that is being created
        :param contents: contents of the file being created
        """
        with tempfile.TemporaryDirectory(
            prefix="debusine-integration-tests-"
        ) as temp_directory:
            artifact_type = ArtifactCategory.PACKAGE_BUILD_LOG

            temp_path = Path(temp_directory)
            (temp_path / filename).write_text(contents)
            files = list(temp_path.iterdir())

            data = {
                "source": "test",
                "version": "1.0",
                "filename": filename,
            }

            return cls._create_artifact(artifact_type, data, files, temp_path)

    @classmethod
    def create_artifact_signing_input(
        cls, filename: str, contents: bytes
    ) -> int:
        """
        Create a signing-input artifact.

        :param filename: filename that is being created
        :param contents: contents of the file being created
        """
        with tempfile.TemporaryDirectory(
            prefix="debusine-integration-tests-"
        ) as temp_directory:
            artifact_type = ArtifactCategory.SIGNING_INPUT

            temp_path = Path(temp_directory)
            (temp_path / filename).parent.mkdir(parents=True, exist_ok=True)
            (temp_path / filename).write_bytes(contents)

            return cls._create_artifact(
                artifact_type, {}, [temp_path / filename], temp_path
            )

    @staticmethod
    def print_work_request_debug_logs(work_request_id: int):
        """Print work request's log's - used for debugging."""
        work_request = Client.execute_command(
            "show-work-request", work_request_id
        )

        for artifact in work_request["artifacts"]:
            if artifact["category"] == ArtifactCategory.WORK_REQUEST_DEBUG_LOGS:
                for path, file_information in artifact["files"].items():
                    print("------ FILE:", path)
                    print(">>>>>>>>>>>")
                    print(requests.get(file_information["url"]).text)
                    print("<<<<<<<<<<<")

    @staticmethod
    def create_system_images_mmdebstrap(
        suites: list[str], backend: str = "unshare"
    ) -> None:
        """
        Create debian:system-image artifacts and add them to a collection.

        :param suites: suites of the system images
        """
        template_data = {
            "vendor": "debian",
            "targets": [
                {
                    "codenames": suites,
                    "backends": [backend],
                    "architectures": [
                        subprocess.check_output(
                            ["dpkg", "--print-architecture"], text=True
                        ).strip()
                    ],
                    "mmdebstrap_template": {
                        "bootstrap_options": {"variant": "apt"},
                        "bootstrap_repositories": [
                            {
                                "mirror": "http://deb.debian.org/debian",
                                "components": ["main"],
                            }
                        ],
                    },
                }
            ],
        }

        workflow_template_result = Client.execute_command(
            "create-workflow-template",
            f"mmdebstrap-environment-{'-'.join(suites)}",
            "update_environments",
            stdin=yaml.safe_dump(template_data),
        )
        assert workflow_template_result.returncode == 0

        workflow_id = Client.execute_command(
            "create-workflow",
            f"mmdebstrap-environment-{'-'.join(suites)}",
            stdin=yaml.safe_dump({}),
        )["workflow_id"]

        # The worker should get the new work request and start executing it
        assert Client.wait_for_work_request_completed(
            workflow_id, "success", timeout=1800 * len(suites)
        )
