"""Tests for canonical issue workflow label policy (#513)."""

from __future__ import annotations

import sys
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

import issue_workflow_labels as labels  # noqa: E402


class TestIssueWorkflowLabelValidation(unittest.TestCase):
    def test_valid_labeled_issue(self):
        result = labels.assess_issue_labels(["type:feature", "status:ready"])
        self.assertTrue(result["valid"])
        self.assertEqual(result["type_labels"], ["type:feature"])
        self.assertEqual(result["status_labels"], ["status:ready"])

    def test_issue_missing_type_label(self):
        result = labels.assess_issue_labels(["status:ready"])
        self.assertFalse(result["valid"])
        self.assertIn("issue is missing a type:* label", result["errors"])

    def test_issue_missing_status_label(self):
        result = labels.assess_issue_labels(["type:bug"])
        self.assertFalse(result["valid"])
        self.assertIn("issue is missing a status:* label", result["errors"])

    def test_discussion_issue_missing_type_discussion(self):
        result = labels.assess_issue_labels(
            ["type:feature", "status:triage"],
            discussion=True,
        )
        self.assertFalse(result["valid"])
        self.assertIn("discussion issue is missing type:discussion", result["errors"])

    def test_multiple_conflicting_status_labels(self):
        result = labels.assess_issue_labels(
            ["type:feature", "status:ready", "status:blocked"]
        )
        self.assertFalse(result["valid"])
        self.assertTrue(
            any("multiple active status:* labels" in err for err in result["errors"])
        )


class TestIssueWorkflowStatusTransitions(unittest.TestCase):
    def test_status_transition_removes_old_status_label(self):
        result = labels.transition_status_labels(
            ["type:feature", "status:ready", "workflow"],
            "in-progress",
        )
        self.assertEqual(result, ["type:feature", "workflow", "status:in-progress"])

    def test_pr_open_transition_applies_status_pr_open(self):
        result = labels.transition_status_labels(
            ["type:feature", "status:in-progress"],
            "pr-open",
        )
        self.assertEqual(result, ["type:feature", "status:pr-open"])

    def test_duplicate_transition_applies_status_duplicate(self):
        result = labels.transition_status_labels(
            ["type:process", "status:triage"],
            "duplicate",
        )
        self.assertEqual(result, ["type:process", "status:duplicate"])


class TestLifecycleRoleLabels(unittest.TestCase):
    def test_role_transition_author_to_reviewer_to_merger_single_active(self):
        after_author = labels.transition_role_labels(
            ["type:feature", "status:pr-open"], "author"
        )
        self.assertEqual(after_author, ["type:feature", "status:pr-open", "role:author"])

        after_reviewer = labels.transition_role_labels(after_author, "reviewer")
        self.assertEqual(
            after_reviewer, ["type:feature", "status:pr-open", "role:reviewer"]
        )
        self.assertNotIn("role:author", after_reviewer)

        after_merger = labels.transition_role_labels(after_reviewer, "merger")
        self.assertEqual(labels.role_labels(after_merger), ["role:merger"])

    def test_role_transition_accepts_canonical_label(self):
        result = labels.transition_role_labels(["type:bug"], "role:reviewer")
        self.assertEqual(result, ["type:bug", "role:reviewer"])

    def test_unknown_role_raises(self):
        with self.assertRaises(ValueError):
            labels.canonical_role_label("wizard")

    def test_assess_reports_multiple_active_role_labels(self):
        result = labels.assess_issue_labels(
            ["type:feature", "status:pr-open", "role:author", "role:reviewer"]
        )
        self.assertFalse(result["valid"])
        self.assertTrue(
            any("multiple active role:* labels" in err for err in result["errors"])
        )
        self.assertEqual(
            sorted(result["role_labels"]), ["role:author", "role:reviewer"]
        )


class TestLifecycleHazardLabels(unittest.TestCase):
    def test_hazards_are_additive_and_multiple(self):
        one = labels.add_hazard_label(["type:bug", "status:blocked"], "conflicted")
        two = labels.add_hazard_label(one, "stale-lease")
        self.assertIn("hazard:conflicted", two)
        self.assertIn("hazard:stale-lease", two)
        # status/type untouched
        self.assertIn("status:blocked", two)
        self.assertIn("type:bug", two)
        self.assertEqual(len(labels.hazard_labels(two)), 2)

    def test_add_hazard_is_idempotent(self):
        once = labels.add_hazard_label(["type:bug", "status:ready"], "root-mutation")
        twice = labels.add_hazard_label(once, "root_mutation")
        self.assertEqual(twice.count("hazard:root-mutation"), 1)

    def test_clear_hazard_leaves_others_intact(self):
        start = ["type:bug", "status:blocked", "hazard:conflicted", "hazard:stale-lease"]
        cleared = labels.clear_hazard_label(start, "conflicted")
        self.assertNotIn("hazard:conflicted", cleared)
        self.assertIn("hazard:stale-lease", cleared)
        self.assertIn("status:blocked", cleared)

    def test_terminal_blocker_hazard_normalizes(self):
        self.assertEqual(
            labels.canonical_hazard_label("terminal-blocker"),
            "hazard:terminal-blocker",
        )

    def test_assess_surfaces_hazard_labels(self):
        result = labels.assess_issue_labels(
            ["type:bug", "status:blocked", "hazard:conflicted"]
        )
        self.assertEqual(result["hazard_labels"], ["hazard:conflicted"])


class TestDiscussionExclusion(unittest.TestCase):
    def test_discussion_issue_excluded_from_implementation_queue(self):
        self.assertTrue(labels.is_discussion(["type:discussion", "status:ready"]))
        self.assertFalse(
            labels.is_implementation_candidate(["type:discussion", "status:ready"])
        )

    def test_non_discussion_issue_is_candidate(self):
        self.assertTrue(
            labels.is_implementation_candidate(["type:feature", "status:ready"])
        )


class TestBlockingReasonRequirement(unittest.TestCase):
    def test_blocked_requires_reason(self):
        self.assertTrue(
            labels.requires_blocking_reason(["type:bug", "status:blocked"])
        )

    def test_hazard_requires_reason(self):
        self.assertTrue(
            labels.requires_blocking_reason(
                ["type:bug", "status:ready", "hazard:stale-lease"]
            )
        )

    def test_clean_ready_issue_needs_no_reason(self):
        self.assertFalse(
            labels.requires_blocking_reason(["type:feature", "status:ready"])
        )


class TestState603SynonymTransitions(unittest.TestCase):
    def test_authoring_maps_to_in_progress(self):
        self.assertEqual(
            labels.canonical_status_label("authoring"), "status:in-progress"
        )

    def test_changes_requested_transition(self):
        result = labels.transition_status_labels(
            ["type:feature", "status:needs-review"], "changes-requested"
        )
        self.assertEqual(result, ["type:feature", "status:changes-requested"])

    def test_merge_ready_maps_to_approved(self):
        self.assertEqual(labels.canonical_status_label("merge-ready"), "status:approved")

    def test_abandoned_maps_to_wontfix(self):
        self.assertEqual(labels.canonical_status_label("abandoned"), "status:wontfix")

    def test_blocked_and_merged_transitions(self):
        blocked = labels.transition_status_labels(
            ["type:bug", "status:in-progress"], "blocked"
        )
        self.assertEqual(blocked, ["type:bug", "status:blocked"])
        merged = labels.transition_status_labels(
            ["type:feature", "status:approved"], "merged"
        )
        self.assertEqual(merged, ["type:feature", "status:reconcile"])


if __name__ == "__main__":
    unittest.main()
