"""Unit tests for canonical_comment_validator (#496)."""

import unittest

import canonical_comment_validator as ccv

FULL_SHA = "a" * 40

_VALID_ISSUE = f"""## Canonical Issue State

STATE: ready-for-author
WHO_IS_NEXT: author
NEXT_ACTION: Implement canonical comment validator and wire MCP gates
NEXT_PROMPT:
```text
You are the author LLM for issue #496. Add canonical_comment_validator.py
and wire fail-closed gates into gitea_create_issue_comment.
```
WHAT_HAPPENED: Issue filed for MCP validation wall
WHY: Workflow comments must carry durable next-action state
RELATED_PRS: none
BLOCKERS: none
VALIDATION: unit tests not yet run
LAST_UPDATED_BY: prgs-author
"""

_VALID_PR = f"""## Canonical PR State

STATE: ready-for-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Run focused pytest and review the validator wiring diff
NEXT_PROMPT:
```text
Review PR for issue #496. Confirm casual comments still post and workflow
comments without canonical fields are rejected before the Gitea API call.
```
WHAT_HAPPENED: Author opened PR with validator module
WHY: Fail-closed enforcement belongs at the MCP boundary
ISSUE: #496
HEAD_SHA: {FULL_SHA}
REVIEW_STATUS: pending
MERGE_READY: false
BLOCKERS: none
VALIDATION: not yet run
LAST_UPDATED_BY: prgs-author
"""

_VALID_SUPERSEDED = """## Canonical PR State

STATE: superseded
WHO_IS_NEXT: reconciler
NEXT_ACTION: Close superseded issue after canonical issue #495 lands
NEXT_PROMPT:
```text
Close issue #494 as superseded by #495 once templates and docs are merged.
```
WHY: Duplicate tracking issue replaced by durable canonical template work
CANONICAL_ITEM: issue #495
SUPERSEDED_ITEM: issue #494
CLOSE_OR_KEEP_OPEN: close superseded issue #494 after #495 merges
"""

_VALID_BLOCKED = """## Canonical Issue State

STATE: blocked
WHO_IS_NEXT: author
NEXT_ACTION: Fix failing validator unit tests before requesting re-review
NEXT_PROMPT:
```text
Run pytest tests/test_canonical_comment_validator.py and repair failures.
```
WHAT_HAPPENED: Validator tests failed in CI
WHY: Blocked until test suite is green
RELATED_PRS: PR #500
BLOCKERS: pytest failures; unblock after all validator tests pass
VALIDATION: pytest failed
LAST_UPDATED_BY: prgs-author
"""

_VALID_READY_TO_MERGE = f"""## Canonical PR State

STATE: ready-to-merge
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR after confirming approval_at_current_head
NEXT_PROMPT:
```text
Merge PR #500 for issue #496 after live mergeable check passes.
```
WHAT_HAPPENED: Reviewer approved at current head
WHY: All gates passed and head SHA is current
ISSUE: #496
HEAD_SHA: {FULL_SHA}
REVIEW_STATUS: approved / approval_at_current_head
MERGE_READY: true
BLOCKERS: none
VALIDATION: pytest passed; reviewer approved at head {FULL_SHA}
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; token_fingerprint=testharmless
LAST_UPDATED_BY: prgs-reviewer
"""


class TestCanonicalCommentDetection(unittest.TestCase):
    def test_casual_comment_not_workflow(self):
        self.assertFalse(ccv.is_workflow_changing_comment("Thanks, I will check this."))

    def test_workflow_trigger_detected(self):
        self.assertTrue(ccv.is_workflow_changing_comment("Blocked, author should fix."))

    def test_machine_marker_exempt(self):
        body = "<!-- mcp-review-lease:v1 -->\nphase: claimed"
        self.assertFalse(ccv.is_workflow_changing_comment(body))


class TestCanonicalCommentValidation(unittest.TestCase):
    def test_casual_comment_allowed(self):
        result = ccv.assess_canonical_comment("Thanks, I will check this.")
        self.assertTrue(result["allowed"])
        self.assertFalse(result["is_workflow_comment"])

    def test_workflow_comment_missing_fields_rejected(self):
        result = ccv.assess_canonical_comment("Blocked, author should fix.")
        self.assertFalse(result["allowed"])
        self.assertTrue(result["is_workflow_comment"])
        self.assertIn("WHO_IS_NEXT", result["missing_fields"])
        self.assertIn("correction_message", result)
        self.assertIn("Suggested template", result["correction_message"])

    def test_missing_next_prompt_rejected(self):
        body = """STATE: blocked
WHO_IS_NEXT: author
NEXT_ACTION: Repair the failing validator unit tests in CI
WHY: Tests must pass before merge
BLOCKERS: pytest failed; unblock after tests pass
VALIDATION: failed
"""
        result = ccv.assess_canonical_comment(body)
        self.assertFalse(result["allowed"])
        self.assertIn("NEXT_PROMPT", result["missing_fields"] + result["vague_fields"])

    def test_vague_next_action_rejected(self):
        body = _VALID_ISSUE.replace(
            "Implement canonical comment validator and wire MCP gates",
            "continue",
        )
        result = ccv.assess_canonical_comment(body)
        self.assertFalse(result["allowed"])
        self.assertIn("NEXT_ACTION", result["vague_fields"])

    def test_invalid_who_is_next_rejected(self):
        body = _VALID_ISSUE.replace("WHO_IS_NEXT: author", "WHO_IS_NEXT: llm")
        result = ccv.assess_canonical_comment(body)
        self.assertFalse(result["allowed"])
        self.assertIn("WHO_IS_NEXT", result["vague_fields"])

    def test_valid_issue_comment_allowed(self):
        result = ccv.assess_canonical_comment(_VALID_ISSUE)
        self.assertTrue(result["allowed"])

    def test_issue_comment_mentioning_pr_requires_related_prs(self):
        body = _VALID_ISSUE.replace("RELATED_PRS: none", "RELATED_PRS:")
        body = body.replace("Issue filed", "Issue filed; see PR #500")
        result = ccv.assess_canonical_comment(body)
        self.assertFalse(result["allowed"])
        self.assertIn("RELATED_PRS", result["missing_fields"])

    def test_pr_review_requires_head_sha(self):
        body = _VALID_PR.replace(f"HEAD_SHA: {FULL_SHA}", "HEAD_SHA:")
        result = ccv.assess_canonical_comment(body, context="pr_review")
        self.assertFalse(result["allowed"])
        self.assertIn("HEAD_SHA", result["missing_fields"])

    def test_ready_to_merge_requires_approval_and_sha_proof(self):
        body = _VALID_READY_TO_MERGE.replace("approval_at_current_head", "pending")
        body = body.replace(f"HEAD_SHA: {FULL_SHA}", "HEAD_SHA: pending")
        body = body.replace("reviewer approved at head", "reviewer pending at head")
        body = body.replace("approved /", "pending /")
        result = ccv.assess_canonical_comment(body, context="pr_comment")
        self.assertFalse(result["allowed"])
        self.assertTrue(result["extra_reasons"])

    def test_ready_to_merge_valid_allowed(self):
        result = ccv.assess_canonical_comment(_VALID_READY_TO_MERGE, context="pr_comment")
        self.assertTrue(result["allowed"])

    def test_superseded_requires_canonical_and_superseded_items(self):
        body = _VALID_SUPERSEDED.replace("CANONICAL_ITEM: issue #495", "CANONICAL_ITEM:")
        result = ccv.assess_canonical_comment(body, context="supersession")
        self.assertFalse(result["allowed"])
        self.assertIn("CANONICAL_ITEM", result["missing_fields"])

    def test_superseded_valid_allowed(self):
        result = ccv.assess_canonical_comment(_VALID_SUPERSEDED, context="supersession")
        self.assertTrue(result["allowed"])

    def test_blocked_requires_unblock_condition(self):
        body = _VALID_BLOCKED.replace(
            "BLOCKERS: pytest failures; unblock after all validator tests pass",
            "BLOCKERS: pytest failures",
        )
        result = ccv.assess_canonical_comment(body)
        self.assertFalse(result["allowed"])
        self.assertIn("BLOCKERS", result["vague_fields"])

    def test_blocked_valid_allowed(self):
        result = ccv.assess_canonical_comment(_VALID_BLOCKED)
        self.assertTrue(result["allowed"])

    def test_correction_lists_missing_fields(self):
        result = ccv.assess_canonical_comment("ready for merge")
        self.assertFalse(result["allowed"])
        message = result["correction_message"]
        self.assertIn("Missing fields", message)
        self.assertIn("WHO_IS_NEXT", message)
        self.assertIn("Suggested template", message)


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