# 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 management command create_workflow."""

import io
from typing import Any, ClassVar

import yaml
from django.core.management import CommandError

from debusine.artifacts.models import TaskTypes
from debusine.client.models import RuntimeParameter
from debusine.db.models import (
    Artifact,
    User,
    WorkRequest,
    WorkflowTemplate,
    Workspace,
    default_workspace,
    system_user,
)
from debusine.django.management.tests import call_command
from debusine.test.django import TestCase


class CreateWorkflowCommandTests(TestCase):
    """Tests for the create_workflow command."""

    artifact: ClassVar[Artifact]
    template: ClassVar[WorkflowTemplate]

    @classmethod
    def setUpTestData(cls) -> None:
        """Set up test fixture."""
        super().setUpTestData()
        cls.artifact = cls.playground.create_source_artifact()
        architectures = ["amd64", "arm64"]
        for architecture in architectures:
            cls.playground.create_debian_environment(
                codename="bookworm", architecture=architecture
            )
        cls.template = WorkflowTemplate.objects.create(
            name="test",
            workspace=cls.playground.get_default_workspace(),
            task_name="sbuild",
            static_parameters={
                "input": {"source_artifact": cls.artifact.id},
                "architectures": architectures,
            },
            runtime_parameters=RuntimeParameter.ANY,
        )

    def assert_workflow(
        self,
        workflow: WorkRequest,
        *,
        workspace: Workspace | None = None,
        created_by: User | None = None,
        runtime_parameters: dict[str, Any] | None = None,
    ) -> None:
        self.assertEqual(workflow.workspace, workspace or default_workspace())
        self.assertEqual(workflow.created_by, created_by or system_user())
        self.assertEqual(workflow.status, WorkRequest.Statuses.PENDING)
        assert workflow.workflow_template is not None
        self.assertEqual(
            workflow.task_data,
            {
                **(runtime_parameters or {}),
                **workflow.workflow_template.static_parameters,
            },
        )

    def test_data_from_file(self) -> None:
        """`create_workflow` accepts data from a file."""
        data = {"target_distribution": "debian:bookworm"}
        data_file = self.create_temporary_file(
            contents=yaml.safe_dump(data).encode()
        )
        stdout, stderr, exit_code = call_command(
            "create_workflow", "test", "--data", str(data_file)
        )

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

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        self.assert_workflow(workflow, runtime_parameters=data)

    def test_data_from_stdin(self) -> None:
        """`create_workflow` accepts data from stdin."""
        data = {"target_distribution": "debian:bookworm"}
        stdout, stderr, exit_code = call_command(
            "create_workflow", "test", stdin=io.StringIO(yaml.safe_dump(data))
        )

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

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        self.assert_workflow(workflow, runtime_parameters=data)

    def test_empty_data(self) -> None:
        """`create_workflow` defaults data to {}."""
        WorkflowTemplate.objects.create(
            name="noop", workspace=default_workspace(), task_name="noop"
        )
        stdout, stderr, exit_code = call_command(
            "create_workflow", "noop", stdin=io.StringIO()
        )

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

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW, task_name="noop"
        )
        self.assert_workflow(workflow, runtime_parameters={})

    def test_different_created_by(self) -> None:
        """`create_workflow` can use a different created-by user."""
        user = self.playground.get_default_user()
        data = {"target_distribution": "debian:bookworm"}
        stdout, stderr, exit_code = call_command(
            "create_workflow",
            "test",
            "--created-by",
            user.username,
            stdin=io.StringIO(yaml.safe_dump(data)),
        )

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

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        self.assert_workflow(workflow, created_by=user, runtime_parameters=data)

    def test_different_workspace(self) -> None:
        """`create_workflow` can use a non-default workspace."""
        workspace_name = "test-workspace"
        workspace = self.playground.create_workspace(
            name=workspace_name, public=True
        )
        workspace.set_inheritance([self.playground.get_default_workspace()])
        self.template.workspace = workspace
        self.template.save()
        data = {"target_distribution": "debian:bookworm"}
        stdout, stderr, exit_code = call_command(
            "create_workflow",
            "test",
            "--workspace",
            workspace_name,
            stdin=io.StringIO(yaml.safe_dump(data)),
        )

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

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        self.assert_workflow(
            workflow, workspace=workspace, runtime_parameters=data
        )

    def test_invalid_data_yaml(self) -> None:
        """`create_workflow` returns error: cannot parse data."""
        with self.assertRaisesRegex(
            CommandError, r"^Error parsing YAML:"
        ) as exc:
            call_command("create_workflow", "test", stdin=io.StringIO(":"))

        self.assertEqual(exc.exception.returncode, 3)

    def test_user_not_found(self) -> None:
        """`create_workflow` returns error: user not found."""
        with self.assertRaisesRegex(
            CommandError, r'^User "nonexistent" not found'
        ) as exc:
            call_command(
                "create_workflow",
                "test",
                "--created-by",
                "nonexistent",
                stdin=io.StringIO(),
            )

        self.assertEqual(exc.exception.returncode, 3)

    def test_workspace_not_found(self) -> None:
        """`create_workflow` returns error: workspace not found."""
        with self.assertRaisesRegex(
            CommandError, r"Workspace 'nonexistent' not found"
        ) as exc:
            call_command(
                "create_workflow",
                "test",
                "--workspace",
                "nonexistent",
                stdin=io.StringIO(),
            )

        self.assertEqual(exc.exception.returncode, 3)

    def test_template_name_not_found(self) -> None:
        """`create_workflow` returns error: template name not found."""
        with self.assertRaisesRegex(
            CommandError, r'^Workflow template "nonexistent" not found'
        ) as exc:
            call_command("create_workflow", "nonexistent", stdin=io.StringIO())

        self.assertEqual(exc.exception.returncode, 3)

    def test_bad_workflow_data(self) -> None:
        """`create_workflow` returns error: bad workflow data."""
        data = {"target_distribution": "debian:bookworm", "foo": "bar"}
        with self.assertRaisesRegex(
            CommandError,
            r"foo\\n"
            r"\s+Extra inputs are not permitted \[type=extra_forbidden",
        ) as exc:
            call_command(
                "create_workflow",
                "test",
                stdin=io.StringIO(yaml.safe_dump(data)),
            )

        self.assertEqual(exc.exception.returncode, 3)
        self.assertFalse(
            WorkRequest.objects.filter(
                task_type=TaskTypes.WORKFLOW, task_name="sbuild"
            ).exists()
        )
