# Copyright © 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.

"""Tests for Debusine CLI suite management commands."""

from typing import Any
from unittest import mock

import yaml

from debusine.artifacts.models import CollectionCategory
from debusine.client.commands.archives.suites import Create
from debusine.client.commands.tests.base import BaseCliTests
from debusine.client.debusine import Debusine
from debusine.client.exceptions import DebusineError
from debusine.client.models import (
    CollectionData,
    CollectionDataNew,
    RuntimeParameter,
    WorkflowTemplateData,
    WorkflowTemplateDataNew,
    WorkspaceInheritanceChain,
    WorkspaceInheritanceChainElement,
)


class CreateTests(BaseCliTests):
    """Tests for ``debusine archive suite create``."""

    def cmdline(self, *args: str) -> list[str]:
        """Shortcut to build a command."""
        return ["archive", "suite", "create", *args]

    def chain(
        self,
        *args: WorkspaceInheritanceChainElement,
    ) -> WorkspaceInheritanceChain:
        """Shortcut to build an inheritance chain."""
        return WorkspaceInheritanceChain(chain=list(args))

    def suite(self, name: str, data: dict[str, Any]) -> CollectionData:
        """Shortcut to build a suite collection."""
        return CollectionData(
            id=1,
            name=name,
            category=CollectionCategory.SUITE,
            data=data,
            links={"webui_self": "/c/1"},
        )

    def workflow_template(
        self,
        name: str,
        task_name: str,
        workflow_template_id: int = 1,
        static_parameters: dict[str, Any] | None = None,
    ) -> WorkflowTemplateData:
        """Shortcut to build a workflow template."""
        return WorkflowTemplateData(
            id=workflow_template_id,
            name=name,
            task_name=task_name,
            static_parameters=static_parameters or {},
            runtime_parameters=RuntimeParameter.ANY,
            links={"webui_self": f"/wt/{workflow_template_id}"},
        )

    def test_base_workflow_template_nonexistent_workspace(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=nonexistent",
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)

        with (
            mock.patch.object(
                Debusine,
                "workflow_template_get",
                side_effect=DebusineError(title="Workspace not found"),
            ),
            mock.patch.object(
                Debusine,
                "get_workspace_inheritance",
                side_effect=DebusineError(title="Workspace not found"),
            ),
            self.assertRaises(DebusineError) as raised,
        ):
            self.capture_output(
                command.get_base_workflow_template, assert_system_exit_code=3
            )

        self.assertDebusineError(
            raised.exception, {"title": "Workspace not found"}
        )

    def test_base_workflow_template_in_different_scope(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)
        El = WorkspaceInheritanceChainElement
        chain = self.chain(El(scope="other-scope", workspace="base"))

        with (
            mock.patch.object(
                Debusine,
                "workflow_template_get",
                side_effect=DebusineError(title="Object not found"),
            ),
            mock.patch.object(
                Debusine, "get_workspace_inheritance", return_value=chain
            ),
        ):
            stderr, _ = self.capture_output(
                command.get_base_workflow_template, assert_system_exit_code=3
            )

        self.assertEqual(
            stderr,
            "Workflow template 'upload-to-unstable' not found in any parent "
            "workspace in this scope.\n",
        )

    def test_base_workflow_template_wrong_task_name(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--base-workflow-template=sample",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)

        with mock.patch.object(
            Debusine,
            "workflow_template_get",
            return_value=self.workflow_template("sample", "noop"),
        ):
            stderr, _ = self.capture_output(
                command.get_base_workflow_template, assert_system_exit_code=3
            )

        self.assertEqual(
            stderr,
            "Workflow template 'sample' is not a debian_pipeline template.\n",
        )

    def test_base_workflow_template_in_current_workspace(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)
        base_workflow_template = self.workflow_template(
            "upload-to-unstable", "debian_pipeline"
        )

        with mock.patch.object(
            Debusine,
            "workflow_template_get",
            return_value=base_workflow_template,
        ) as workflow_template_get:
            self.assertEqual(
                command.get_base_workflow_template(), base_workflow_template
            )

        workflow_template_get.assert_called_once_with(
            "developers", "upload-to-unstable"
        )

    def test_base_workflow_template_in_parent_workspace(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)
        El = WorkspaceInheritanceChainElement
        chain = self.chain(El(scope=self.default_server, workspace="base"))
        base_workflow_template = self.workflow_template(
            "upload-to-unstable", "debian_pipeline"
        )

        with (
            mock.patch.object(
                Debusine,
                "workflow_template_get",
                side_effect=iter(
                    [
                        DebusineError(title="Object not found"),
                        base_workflow_template,
                    ]
                ),
            ) as workflow_template_get,
            mock.patch.object(
                Debusine, "get_workspace_inheritance", return_value=chain
            ),
        ):
            self.assertEqual(
                command.get_base_workflow_template(), base_workflow_template
            )

        self.assertEqual(workflow_template_get.call_count, 2)
        workflow_template_get.assert_has_calls(
            [
                mock.call("workspace", "upload-to-unstable"),
                mock.call("base", "upload-to-unstable"),
            ]
        )

    def test_adjust_static_parameters_with_architecture_all(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=all",
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)

        self.assertEqual(
            command.adjust_static_parameters(
                {"vendor": "debian", "enable_upload": True}
            ),
            {
                "vendor": "debian",
                "suite": f"sid-overlay@{CollectionCategory.SUITE}",
                "architectures_allowlist": ["all", "amd64"],
            },
        )

    def test_adjust_static_parameters_without_architecture_all(self) -> None:
        command = self.create_command(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--architecture=i386",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        assert isinstance(command, Create)

        self.assertEqual(
            command.adjust_static_parameters(
                {"vendor": "debian", "enable_upload": True}
            ),
            {
                "vendor": "debian",
                "suite": f"sid-overlay@{CollectionCategory.SUITE}",
                "architectures_allowlist": ["all", "amd64", "i386"],
            },
        )

    def test_run_create_collection_error(self) -> None:
        cli = self.create_cli(
            self.cmdline(
                "--workspace=workspace", "--architecture=amd64", "sid-overlay"
            )
        )

        with mock.patch.object(
            Debusine,
            "collection_create",
            side_effect=DebusineError(title="expected error"),
        ):
            exception = self.assertShowsError(cli.execute)

        self.assertDebusineError(exception, {"title": "expected error"})

    def test_run_create_workflow_template_error(self) -> None:
        cli = self.create_cli(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        base_workflow_template = self.workflow_template(
            "upload-to-unstable", "debian_pipeline"
        )
        suite = self.suite(
            "sid-overlay", {"components": "main", "architectures": ["amd64"]}
        )

        with (
            mock.patch.object(
                Debusine,
                "workflow_template_get",
                return_value=base_workflow_template,
            ),
            mock.patch.object(
                Debusine, "collection_create", return_value=suite
            ),
            mock.patch.object(
                Debusine,
                "workflow_template_create",
                side_effect=DebusineError(title="expected error"),
            ),
        ):
            exception = self.assertShowsError(cli.execute)

        self.assertDebusineError(exception, {"title": "expected error"})

    def test_run_without_base_workflow_template(self) -> None:
        cli = self.create_cli(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--architecture=i386",
                "sid-overlay",
            )
        )
        expected_suite_data = {
            "components": ["main"],
            "architectures": ["amd64", "i386"],
        }
        suite = self.suite("sid-overlay", expected_suite_data)

        with mock.patch.object(
            Debusine, "collection_create", return_value=suite
        ) as collection_create:
            stderr, stdout = self.capture_output(cli.execute)

        collection_create.assert_called_once_with(
            "workspace",
            CollectionDataNew(
                name="sid-overlay",
                category=CollectionCategory.SUITE,
                data=expected_suite_data,
            ),
        )
        self.assertEqual(stderr, "")
        self.assertEqual(
            yaml.safe_load(stdout),
            {"result": "success", "suite": "https://debusine.debian.org/c/1"},
        )

    def test_run_with_base_workflow_template(self) -> None:
        cli = self.create_cli(
            self.cmdline(
                "--workspace=workspace",
                "--architecture=amd64",
                "--architecture=i386",
                "--base-workflow-template=upload-to-unstable",
                "sid-overlay",
            )
        )
        base_workflow_template = self.workflow_template(
            "upload-to-unstable",
            "debian_pipeline",
            static_parameters={"vendor": "debian", "enable_upload": True},
        )
        expected_suite_data = {
            "components": ["main"],
            "architectures": ["amd64", "i386"],
        }
        suite = self.suite("sid-overlay", expected_suite_data)
        expected_static_parameters = {
            "vendor": "debian",
            "suite": f"sid-overlay@{CollectionCategory.SUITE}",
            "architectures_allowlist": ["all", "amd64", "i386"],
        }
        workflow_template = self.workflow_template(
            "publish-to-sid-overlay",
            "debian_pipeline",
            workflow_template_id=2,
            static_parameters=expected_static_parameters,
        )

        with (
            mock.patch.object(
                Debusine,
                "workflow_template_get",
                return_value=base_workflow_template,
            ),
            mock.patch.object(
                Debusine, "collection_create", return_value=suite
            ) as collection_create,
            mock.patch.object(
                Debusine,
                "workflow_template_create",
                return_value=workflow_template,
            ) as workflow_template_create,
        ):
            stderr, stdout = self.capture_output(cli.execute)

        collection_create.assert_called_once_with(
            "workspace",
            CollectionDataNew(
                name="sid-overlay",
                category=CollectionCategory.SUITE,
                data=expected_suite_data,
            ),
        )
        workflow_template_create.assert_called_once_with(
            "workspace",
            WorkflowTemplateDataNew(
                name="publish-to-sid-overlay",
                task_name="debian_pipeline",
                static_parameters=expected_static_parameters,
                runtime_parameters=RuntimeParameter.ANY,
                priority=0,
            ),
        )
        self.assertEqual(stderr, "")
        self.assertEqual(
            yaml.safe_load(stdout),
            {
                "result": "success",
                "suite": "https://debusine.debian.org/c/1",
                "workflow_template": "https://debusine.debian.org/wt/2",
            },
        )
