"""#727 / PR #728: author ownership when tracking issue number != PR number.

Regression for REQUEST_CHANGES: gitea_update_pr_branch_by_merge must not require
issue_number == pr_number. Ownership is proven via linked issue + lock/branch
context and fails closed for unrelated issues.
"""

from __future__ import annotations

import os
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from unittest.mock import patch

import issue_lock_store
import gitea_mcp_server as mcp


def _live_lock(
    *,
    issue_number: int,
    branch_name: str = "feat/issue-727-pr-sync-status",
    worktree_path: str = "/tmp/branches/issue-727-pr-sync-status",
    remote: str = "prgs",
    org: str = "Scaled-Tech-Consulting",
    repo: str = "Gitea-Tools",
) -> dict:
    now = datetime.now(timezone.utc)
    return {
        "issue_number": issue_number,
        "branch_name": branch_name,
        "worktree_path": worktree_path,
        "remote": remote,
        "org": org,
        "repo": repo,
        "operation_type": issue_lock_store.AUTHOR_ISSUE_WORK_LEASE,
        "acquired_at": now.isoformat(),
        "expires_at": (now + timedelta(hours=2)).isoformat(),
        "owner_pid": os.getpid(),
        "status": "active",
    }


class TestAuthorOwnershipIssuePrMismatch(unittest.TestCase):
    def setUp(self):
        self._tmp = tempfile.TemporaryDirectory(prefix="gitea-issue-locks-")
        self.lock_dir = self._tmp.name
        os.environ["GITEA_ISSUE_LOCK_DIR"] = self.lock_dir

    def tearDown(self):
        # Clear session pointer for this process.
        try:
            ptr = issue_lock_store.session_pointer_path(self.lock_dir)
            if os.path.isfile(ptr):
                os.unlink(ptr)
        except Exception:
            pass
        os.environ.pop("GITEA_ISSUE_LOCK_DIR", None)
        self._tmp.cleanup()

    def test_issue_727_pr_728_session_lock_accepted(self):
        """Tracking issue #727 lock is valid ownership for PR #728."""
        lock = _live_lock(issue_number=727)
        issue_lock_store.bind_session_lock(lock)
        result = mcp._prove_author_ownership_for_pr(
            pr_number=728,
            pr_title="feat: pr sync",
            pr_body="Closes #727\n\nNative PR sync lifecycle.",
            source_branch="feat/issue-727-pr-sync-status",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            worktree_path=lock["worktree_path"],
        )
        self.assertTrue(result["proven"], result)
        self.assertTrue(result["has_author_lock"])
        self.assertEqual(result["matched_issue"], 727)
        self.assertEqual(result["matched_via"], "session_lock")
        self.assertIn(727, result["linked_issues"])

    def test_issue_727_pr_728_durable_lock_accepted(self):
        lock = _live_lock(issue_number=727)
        path = issue_lock_store.lock_file_path(
            remote="prgs",
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            issue_number=727,
            lock_dir=self.lock_dir,
        )
        issue_lock_store.save_lock_file(path, lock)
        result = mcp._prove_author_ownership_for_pr(
            pr_number=728,
            pr_title="feat: pr sync",
            pr_body="Fixes #727",
            source_branch="feat/issue-727-pr-sync-status",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
        )
        self.assertTrue(result["proven"], result)
        self.assertEqual(result["matched_issue"], 727)
        self.assertEqual(result["matched_via"], "durable_lock")

    def test_legacy_same_number_still_accepted(self):
        lock = _live_lock(
            issue_number=100,
            branch_name="fix/issue-100",
            worktree_path="/tmp/branches/issue-100",
        )
        issue_lock_store.bind_session_lock(lock)
        result = mcp._prove_author_ownership_for_pr(
            pr_number=100,
            pr_title="fix something",
            pr_body="Closes #100",
            source_branch="fix/issue-100",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            worktree_path=lock["worktree_path"],
        )
        self.assertTrue(result["proven"], result)
        self.assertEqual(result["matched_issue"], 100)

    def test_unrelated_issue_lock_rejected(self):
        """Lock for issue #999 must not authorize PR #728 linked only to #727."""
        lock = _live_lock(
            issue_number=999,
            branch_name="feat/issue-999",
            worktree_path="/tmp/branches/issue-999",
        )
        issue_lock_store.bind_session_lock(lock)
        path = issue_lock_store.lock_file_path(
            remote="prgs",
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            issue_number=999,
            lock_dir=self.lock_dir,
        )
        issue_lock_store.save_lock_file(path, lock)
        result = mcp._prove_author_ownership_for_pr(
            pr_number=728,
            pr_title="feat: pr sync",
            pr_body="Closes #727",
            source_branch="feat/issue-727-pr-sync-status",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            worktree_path=lock["worktree_path"],
        )
        self.assertFalse(result["proven"], result)
        self.assertFalse(result["has_author_lock"])
        self.assertIsNone(result["matched_issue"])
        self.assertTrue(result["reasons"])

    def test_branch_mismatch_on_session_lock_fail_closed(self):
        lock = _live_lock(
            issue_number=727,
            branch_name="feat/other-branch",
        )
        issue_lock_store.bind_session_lock(lock)
        result = mcp._prove_author_ownership_for_pr(
            pr_number=728,
            pr_title="feat: pr sync",
            pr_body="Closes #727",
            source_branch="feat/issue-727-pr-sync-status",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
            worktree_path=lock["worktree_path"],
        )
        self.assertFalse(result["proven"], result)
        self.assertTrue(any("branch" in r for r in result["reasons"]))

    def test_no_lock_fail_closed(self):
        result = mcp._prove_author_ownership_for_pr(
            pr_number=728,
            pr_title="feat: pr sync",
            pr_body="Closes #727",
            source_branch="feat/issue-727-pr-sync-status",
            remote="prgs",
            host=None,
            org="Scaled-Tech-Consulting",
            repo="Gitea-Tools",
        )
        self.assertFalse(result["proven"], result)
        self.assertTrue(any("no live author issue lock" in r for r in result["reasons"]))


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