# 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 Mapping
from typing import Any, TypeAlias

from debusine.db.models import Artifact, WorkRequest
from debusine.server.workflows.tests.helpers import WorkflowTestCase
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, WorkflowTestCase)
        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.task_class.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: Mapping[
            str, tuple[RegressionAnalysisStatus, dict[str, Any] | None]
        ],
    ) -> None:
        assert isinstance(self, WorkflowTestCase)
        assert workflow.output_data is not None
        self.assertEqual(
            workflow.output_data.regression_analysis,
            {
                key: RegressionAnalysis(
                    original_source_version=(
                        reference_source_version
                        if key in reference_results or key == ""
                        else None
                    ),
                    original_artifact_id=(
                        reference_results[key].id
                        if key in reference_results
                        else None
                    ),
                    new_source_version=(
                        new_source_version
                        if key in new_results or key == ""
                        else None
                    ),
                    new_artifact_id=(
                        new_results[key].id if key in new_results else None
                    ),
                    status=status,
                    details=details,
                )
                for key, (status, details) in expected_analysis.items()
            },
        )

    def assert_callback_status(
        self,
        workflow: WorkRequest,
        expected_callback_status: WorkRequest.Statuses,
    ) -> None:
        assert isinstance(self, WorkflowTestCase)
        self.assertQuerySetEqual(
            (
                workflow.children.workflow_callbacks()
                .order_by("workflow_data_json__display_name")
                .values_list("workflow_data_json__display_name", "status")
            ),
            [("Regression analysis", expected_callback_status)],
        )
