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

"""Common tests for workflows that handle regression tracking."""

from collections.abc import Sequence
from typing import Any, TypeAlias

from debusine.artifacts.models import TaskTypes
from debusine.db.models import Artifact, WorkRequest
from debusine.server.workflows.tests.helpers import WorkflowTestBase
from debusine.tasks.models import RegressionAnalysis, RegressionAnalysisStatus


class RegressionTrackingWorkflowTestMixin:
    """Common tests for workflows that handle regression tracking."""

    def test_compare_qa_results(self) -> None:
        assert isinstance(self, WorkflowTestBase)
        S: TypeAlias = WorkRequest.Statuses
        R: TypeAlias = WorkRequest.Results
        RAS: TypeAlias = RegressionAnalysisStatus
        for ref_status, ref_result, new_status, new_result, expected in (
            (None, None, None, None, RAS.NO_RESULT),
            (None, None, S.COMPLETED, R.SUCCESS, RAS.NO_RESULT),
            (S.COMPLETED, R.SUCCESS, None, None, RAS.NO_RESULT),
            (S.COMPLETED, R.SUCCESS, S.COMPLETED, R.SUCCESS, RAS.STABLE),
            (S.COMPLETED, R.SUCCESS, S.COMPLETED, R.FAILURE, RAS.REGRESSION),
            (S.COMPLETED, R.SUCCESS, S.COMPLETED, R.ERROR, RAS.REGRESSION),
            (S.COMPLETED, R.SUCCESS, S.COMPLETED, R.SKIPPED, RAS.STABLE),
            (S.COMPLETED, R.FAILURE, S.COMPLETED, R.SUCCESS, RAS.IMPROVEMENT),
            (S.COMPLETED, R.FAILURE, S.COMPLETED, R.FAILURE, RAS.STABLE),
            (S.COMPLETED, R.FAILURE, S.COMPLETED, R.ERROR, RAS.ERROR),
            (S.COMPLETED, R.FAILURE, S.COMPLETED, R.SKIPPED, RAS.IMPROVEMENT),
            (S.COMPLETED, R.ERROR, S.COMPLETED, R.SUCCESS, RAS.IMPROVEMENT),
            (S.COMPLETED, R.ERROR, S.COMPLETED, R.ERROR, RAS.STABLE),
            (S.COMPLETED, R.ERROR, S.COMPLETED, R.FAILURE, RAS.ERROR),
            (S.COMPLETED, R.ERROR, S.COMPLETED, R.SKIPPED, RAS.IMPROVEMENT),
            (S.COMPLETED, R.SKIPPED, S.COMPLETED, R.SUCCESS, RAS.STABLE),
            (S.COMPLETED, R.SKIPPED, S.COMPLETED, R.FAILURE, RAS.REGRESSION),
            (S.COMPLETED, R.SKIPPED, S.COMPLETED, R.ERROR, RAS.REGRESSION),
            (S.COMPLETED, R.SKIPPED, S.COMPLETED, R.SKIPPED, RAS.STABLE),
            (S.ABORTED, None, S.COMPLETED, R.SUCCESS, RAS.ERROR),
            (S.COMPLETED, R.SUCCESS, S.ABORTED, None, RAS.ERROR),
        ):
            with self.subTest(
                ref_status=repr(ref_status),
                ref_result=repr(ref_result),
                new_status=repr(new_status),
                new_result=repr(new_result),
            ):
                if ref_status is None:
                    reference = None
                else:
                    reference = self.playground.create_worker_task()
                    reference.status = ref_status
                    if ref_result is not None:
                        reference.result = ref_result
                    reference.save()
                if new_status is None:
                    new = None
                else:
                    new = self.playground.create_worker_task()
                    new.status = new_status
                    if new_result is not None:
                        new.result = new_result
                    new.save()
                self.assertEqual(
                    self.workflow_type.compare_qa_results(reference, new),
                    expected,
                )

    def assert_regression_analysis(
        self,
        workflow: WorkRequest,
        reference_source_version: str,
        reference_results: dict[str, Artifact],
        new_source_version: str,
        new_results: dict[str, Artifact],
        expected_analysis: Sequence[
            tuple[str, RegressionAnalysisStatus, dict[str, Any] | None]
        ],
    ) -> None:
        assert isinstance(self, WorkflowTestBase)
        assert workflow.output_data is not None
        self.assertEqual(
            workflow.output_data.regression_analysis,
            {
                architecture: RegressionAnalysis(
                    original_source_version=(
                        reference_source_version
                        if architecture in reference_results
                        else None
                    ),
                    original_artifact_id=(
                        reference_results[architecture].id
                        if architecture in reference_results
                        else None
                    ),
                    new_source_version=new_source_version,
                    new_artifact_id=new_results[architecture].id,
                    status=status,
                    details=details,
                )
                for architecture, status, details in expected_analysis
            },
        )

    def assert_callback_statuses(
        self,
        workflow: WorkRequest,
        expected_callback_statuses: dict[str, WorkRequest.Statuses],
    ) -> None:
        assert isinstance(self, WorkflowTestBase)
        self.assertQuerySetEqual(
            (
                workflow.children.filter(
                    task_type=TaskTypes.INTERNAL, task_name="workflow"
                )
                .order_by("workflow_data_json__display_name")
                .values_list("workflow_data_json__display_name", "status")
            ),
            [
                (f"Regression analysis for {architecture}", status)
                for architecture, status in expected_callback_statuses.items()
            ],
        )
