"""Tests for audit vs cleanup reconciliation mode (#419)."""
import os
import sys
import unittest
from unittest.mock import patch

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

import audit_reconciliation_mode as arm
import mcp_server
from audit_reconciliation_mode import (
    AUDIT_FORBIDDEN_TASKS,
    PHASE_AUDIT,
    PHASE_CLEANUP,
    assess_audit_command_allowed,
    assess_audit_reconciliation_report,
    authorize_cleanup_phase,
    check_audit_mutation_allowed,
    check_cleanup_execution_allowed,
    classify_cleanup_mutation,
    clear_phase,
    enter_audit_phase,
)
from final_report_validator import assess_final_report_validator
from review_proofs import assess_audit_reconciliation_report as proofs_assess
from task_capability_map import required_permission, required_role

DELETE_PROFILE = {
    # #729: delete_branch is reconciler-owned.
    "profile_name": "prgs-reconciler-delete",
    "role": "reconciler",
    "allowed_operations": [
        "gitea.read",
        "gitea.pr.create",
        "gitea.branch.push",
        "gitea.branch.delete",
    ],
    "forbidden_operations": [],
    "audit_label": "prgs-author-delete",
}

READ_PROFILE = {
    "profile_name": "prgs-author",
    "allowed_operations": ["gitea.read", "gitea.issue.comment"],
    "forbidden_operations": ["gitea.branch.delete"],
    "audit_label": "prgs-author",
}

READ_ENV = {
    "GITEA_MCP_CONFIG": os.path.join(
        os.path.dirname(__file__), "..", "profiles.json"
    ),
    "GITEA_MCP_PROFILE": "prgs-author",
}


def _authorize_cleanup(**kwargs):
    defaults = {
        "operator_approved": True,
        "delete_capability_proven": True,
        "safety_proof": {"safe_to_delete_remote": True},
        "before_after_snapshot": {
            "before": "remote branch exists",
            "after": "remote branch absent",
        },
    }
    defaults.update(kwargs)
    return authorize_cleanup_phase(**defaults)


def _cleanup_report(**overrides):
    base = (
        "Task mode: reconcile-landed-pr\n"
        "Workflow source: workflows/reconcile-landed-pr.md\n"
        "Audit phase: read-only assessment\n"
        "Cleanup phase authorized: true\n"
        "Delete-branch capability proven: true\n"
        "Branch safe to remove: true\n"
        "Before/after state snapshot: remote branch feat/x present → absent\n"
        "External-state mutations: deleted remote branch feat/x\n"
        "Git ref mutations: none\n"
        "Cleanup mutations: removed worktree branches/feat-x\n"
    )
    for key, value in overrides.items():
        base = base.replace(key, value)
    return base


class TestAuditPhaseGates(unittest.TestCase):
    def setUp(self):
        clear_phase()
        enter_audit_phase("reconcile-landed-pr")

    def tearDown(self):
        clear_phase()

    def test_audit_blocks_delete_branch_task(self):
        allowed, reasons = check_audit_mutation_allowed("delete_branch")
        self.assertFalse(allowed)
        self.assertTrue(reasons)

    def test_audit_blocks_worktree_shell_commands(self):
        allowed, reasons = assess_audit_command_allowed(
            "git worktree remove branches/feat-x"
        )
        self.assertFalse(allowed)
        self.assertTrue(reasons)

    def test_audit_blocks_local_branch_delete_command(self):
        allowed, reasons = assess_audit_command_allowed("git branch -D feat/x")
        self.assertFalse(allowed)

    def test_audit_allows_read_tasks(self):
        allowed, _ = check_audit_mutation_allowed("reconcile_landed_pr")
        self.assertTrue(allowed)

    def test_forbidden_set_covers_issue_and_pr_mutations(self):
        for task in ("close_pr", "comment_issue", "commit_files", "push_branch"):
            self.assertIn(task, AUDIT_FORBIDDEN_TASKS)


class TestCleanupAuthorization(unittest.TestCase):
    def setUp(self):
        clear_phase()
        enter_audit_phase("reconcile_merged_cleanups")

    def tearDown(self):
        clear_phase()

    def test_cleanup_without_approval_blocked(self):
        result = authorize_cleanup_phase(
            delete_capability_proven=True,
            safety_proof={"safe_to_delete_remote": True},
            before_after_snapshot={"before": "a", "after": "b"},
        )
        self.assertFalse(result["authorized"])

    def test_cleanup_without_capability_proof_blocked(self):
        result = _authorize_cleanup(delete_capability_proven=False)
        self.assertFalse(result["authorized"])

    def test_cleanup_without_snapshot_blocked(self):
        result = _authorize_cleanup(before_after_snapshot={"before": "", "after": ""})
        self.assertFalse(result["authorized"])

    def test_authorized_cleanup_switches_phase(self):
        result = _authorize_cleanup()
        self.assertTrue(result["authorized"])
        self.assertEqual(result["phase"], PHASE_CLEANUP)

    def test_cleanup_execution_allowed_only_after_authorization(self):
        self.assertFalse(check_cleanup_execution_allowed()[0])
        _authorize_cleanup()
        self.assertTrue(check_cleanup_execution_allowed()[0])


class TestReportVerifier(unittest.TestCase):
    def test_false_no_mutations_after_cleanup_blocked(self):
        report = (
            "Task mode: reconcile-landed-pr\n"
            "No mutations performed.\n"
            "delete_remote_branch feat/dup\n"
        )
        result = assess_audit_reconciliation_report(report)
        self.assertFalse(result["proven"])
        self.assertIn("no mutations", result["reasons"][0].lower())

    def test_cleanup_without_authorization_fields_blocked(self):
        report = (
            "Task mode: reconcile-landed-pr\n"
            "remove_local_worktree branches/feat-x\n"
        )
        result = assess_audit_reconciliation_report(report)
        self.assertFalse(result["proven"])

    def test_authorized_cleanup_report_passes(self):
        result = assess_audit_reconciliation_report(_cleanup_report())
        self.assertTrue(result["proven"])

    def test_mutation_classification_enforced(self):
        report = (
            "Task mode: reconcile-landed-pr\n"
            "Cleanup phase authorized: true\n"
            "Delete-branch capability proven: true\n"
            "Branch safe to remove: true\n"
            "Before/after state snapshot: present\n"
            "remove_local_worktree branches/feat-x\n"
        )
        result = assess_audit_reconciliation_report(report)
        self.assertFalse(result["proven"])

    def test_proofs_export_matches_module(self):
        report = "No mutations performed.\ndelete_remote_branch feat/dup"
        self.assertEqual(
            proofs_assess(report)["proven"],
            assess_audit_reconciliation_report(report)["proven"],
        )

    def test_final_report_validator_includes_boundary_rule(self):
        report = "No mutations performed.\ndelete_remote_branch feat/dup"
        result = assess_final_report_validator(
            report_text=report,
            task_kind="reconcile_already_landed",
        )
        self.assertTrue(result["blocked"])
        rule_ids = [f["rule_id"] for f in result["findings"]]
        self.assertIn("reconcile.audit_cleanup_boundary", rule_ids)


class TestMutationClassification(unittest.TestCase):
    def test_remote_delete_is_external_state(self):
        self.assertEqual(
            classify_cleanup_mutation("delete_remote_branch"),
            "external-state",
        )

    def test_worktree_remove_is_cleanup(self):
        self.assertEqual(
            classify_cleanup_mutation("remove_local_worktree"),
            "cleanup",
        )


class TestMcpGates(unittest.TestCase):
    def setUp(self):
        clear_phase()
        enter_audit_phase("reconcile_merged_cleanups")
        self.mock_api = patch("mcp_server.api_request").start()
        self.mock_auth = patch(
            "mcp_server.get_auth_header", return_value="token test"
        ).start()

    def tearDown(self):
        patch.stopall()
        clear_phase()
        mcp_server._IDENTITY_CACHE.clear()

    @patch.dict(os.environ, READ_ENV, clear=True)
    @patch("mcp_server.get_profile", return_value=DELETE_PROFILE)
    def test_delete_branch_blocked_in_audit_phase(self, _profile):
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check("capability", resolved_role="reconciler")
        result = mcp_server.gitea_delete_branch(branch="feat/dup", remote="prgs")
        self.assertFalse(result["success"])
        self.assertEqual(result["audit_phase"], PHASE_AUDIT)
        self.mock_api.assert_not_called()

    @patch.dict(os.environ, READ_ENV, clear=True)
    @patch("mcp_server.get_profile", return_value=DELETE_PROFILE)
    def test_reconcile_execute_blocked_without_cleanup_auth(self, _profile):
        with self.assertRaises(ValueError):
            mcp_server.gitea_reconcile_merged_cleanups(
                dry_run=False,
                execute_confirmed=False,
                remote="prgs",
            )

    @patch.dict(os.environ, READ_ENV, clear=True)
    @patch("mcp_server.get_profile", return_value=READ_PROFILE)
    def test_cleanup_auth_fails_without_delete_capability(self, _profile):
        result = mcp_server.gitea_authorize_reconciliation_cleanup_phase(
            operator_approved=True,
            delete_capability_proven=True,
            safe_to_delete_remote=True,
            before_state="exists",
            after_state="gone",
        )
        self.assertFalse(result["authorized"])
        self.assertFalse(result["delete_capability_verified"])

    @patch.dict(os.environ, READ_ENV, clear=True)
    @patch("mcp_server.get_profile", return_value=DELETE_PROFILE)
    def test_delete_branch_allowed_after_cleanup_authorization(self, _profile):
        mcp_server.gitea_authorize_reconciliation_cleanup_phase(
            operator_approved=True,
            delete_capability_proven=True,
            safe_to_delete_remote=True,
            before_state="exists",
            after_state="gone",
        )
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check("capability", resolved_role="reconciler")
        self.mock_api.return_value = {}
        result = mcp_server.gitea_delete_branch(branch="feat/dup", remote="prgs")
        self.assertTrue(result["success"])


class TestTaskCapabilityMap(unittest.TestCase):
    def test_reconciliation_cleanup_maps_delete_permission(self):
        self.assertEqual(
            required_permission("reconciliation_cleanup"),
            "gitea.branch.delete",
        )
        self.assertEqual(required_role("reconciliation_cleanup"), "reconciler")


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