# 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 the debusine Cli Workflow commands."""

from pathlib import Path
from textwrap import dedent
from typing import Any, override
from unittest import mock

import yaml

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 CreateWorkflowRequest
from debusine.test.test_utils import create_work_request_response


class StartTests(BaseCliTests):
    """Tests for the CLI `workflow start` command."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.task_data: dict[str, Any] = {"input": {"source_artifact_id": 1}}

    def get_input_task_data_file(self) -> Path:
        """Write self.task_data to a temporary file and return its path."""
        return self.create_temporary_file(
            contents=yaml.safe_dump(self.task_data).encode("utf-8")
        )

    def test_invalid_template_name(self) -> None:
        """CLI fails if the template name is bad."""
        template_name = "template-name"

        self.enterContext(self.patch_sys_stdin_read("{}"))
        cli = self.create_cli(["workflow", "start", template_name])

        with mock.patch.object(
            Debusine,
            "workflow_create",
            autospec=True,
            side_effect=DebusineError(title=f"invalid {template_name}"),
        ):
            exception = self.assertShowsError(cli.execute)

        self.assertDebusineError(
            exception, {"title": f"invalid {template_name}"}
        )

    def assert_create_workflow(
        self, cli_args: list[str], expected_request: CreateWorkflowRequest
    ) -> None:
        """
        Run workflow start with the given arguments, and check its behaviour.

        :param expected_request: request expected to be generated by the command
        """
        cli = self.create_cli(["workflow", "start", *cli_args])

        response = create_work_request_response(
            base_url=self.get_base_url(self.default_server),
            scope=self.servers[self.default_server]["scope"],
            id=11,
            workspace="test",
        )

        with (
            mock.patch.object(
                Debusine,
                "workflow_create",
                return_value=response,
            ) as workflow_create,
            mock.patch(
                "debusine.client.commands.base.Command.feedback"
            ) as feedback,
            mock.patch(
                "debusine.client.commands.work_requests"
                ".WorkRequestModelCommand.show"
            ) as show,
        ):
            stderr, stdout = self.capture_output(cli.execute)

        workflow_create.assert_called_once_with(expected_request)
        feedback.assert_called_once_with("Workflow started:")
        show.assert_called_once_with(response)

        self.assertEqual(stdout, "")
        self.assertEqual(stderr, "")

    def test_specific_workspace(self) -> None:
        """CLI parses the command line and uses --workspace."""
        template_name = "sbuild-amd64-arm64"
        workspace_name = "Testing"
        expected_request = CreateWorkflowRequest(
            template_name=template_name,
            workspace=workspace_name,
            task_data=self.task_data,
        )
        args = [
            template_name,
            "--workspace",
            workspace_name,
            "--data",
            self.get_input_task_data_file().as_posix(),
        ]
        self.assert_create_workflow(args, expected_request)

    def test_success_data_from_file(self) -> None:
        """CLI creates a workflow with data from a file."""
        expected_request = CreateWorkflowRequest(
            template_name="sbuild-amd64-arm64",
            workspace="developers",
            task_data=self.task_data,
        )
        args = [
            expected_request.template_name,
            "--data",
            self.get_input_task_data_file().as_posix(),
        ]
        self.assert_create_workflow(args, expected_request)

    def test_success_data_from_stdin(self) -> None:
        """CLI creates a workflow with data from stdin."""
        expected_request = CreateWorkflowRequest(
            template_name="sbuild-amd64-arm64",
            workspace="developers",
            task_data=self.task_data,
        )
        self.enterContext(
            self.patch_sys_stdin_read(yaml.safe_dump(self.task_data))
        )
        args = ["sbuild-amd64-arm64", "--data", "-"]
        self.assert_create_workflow(args, expected_request)

    def test_data_is_empty(self) -> None:
        """CLI creates a workflow with empty data."""
        empty_file = self.create_temporary_file()
        cli = self.create_cli(
            [
                "workflow",
                "start",
                "sbuild-amd64-arm64",
                "--data",
                str(empty_file),
            ]
        )

        stderr, stdout = self.capture_output(
            cli.execute, assert_system_exit_code=3
        )

        self.assertEqual(
            stderr, "Error: data must be a dictionary. It is empty\n"
        )
        self.assertEqual(stdout, "")

    def test_no_template(self) -> None:
        """CLI creates a workflow directly, without a template."""
        task_name = "package_publish"
        self.task_data = {
            "source_artifact": 1,
            "target_suite": "trixie@debian:suite",
        }
        expected_request = CreateWorkflowRequest(
            task_name=task_name,
            workspace="developers",
            task_data=self.task_data,
        )
        args = [
            task_name,
            "--no-template",
            "--data",
            self.get_input_task_data_file().as_posix(),
        ]
        self.assert_create_workflow(args, expected_request)

    def test_yaml_errors_failed(self) -> None:
        """cli.execute() deals with different invalid task_data."""
        workflows = [
            {
                "task_data": dedent(
                    """\
                    test:
                      name: a-name
                        first-name: some first name
                    """
                ),
                "comment": "yaml.safe_load raises ScannerError",
            },
            {
                "task_data": dedent(
                    """\
                    input:
                      source_url: https://example.com
                     )
                    """
                ),
                "comment": "yaml.safe_load raises ParserError",
            },
        ]

        for workflow in workflows:
            task_data = workflow["task_data"]
            with self.subTest(task_data), self.patch_sys_stdin_read(task_data):
                cli = self.create_cli(["workflow", "start", "workflow-name"])
                stderr, _ = self.capture_output(
                    cli.execute, assert_system_exit_code=3
                )

                self.assertRegex(stderr, "^Error parsing YAML:")
                self.assertRegex(stderr, "Fix the YAML data\n$")
