"""Git client: mock + live GitHub Git Data API (Phase 3b)."""

from __future__ import annotations

import hashlib
from typing import Dict, List, Optional, Protocol

import httpx
from pydantic import BaseModel

from packages.config import Settings, get_settings


class GitResult(BaseModel):
    branch_name: str
    commit_sha: str
    pr_url: str


class GitClient(Protocol):
    async def create_draft_pr(
        self,
        *,
        jira_key: str,
        branch_name: str,
        commit_message: str,
        files: Dict[str, str],
    ) -> GitResult: ...


class MockGitClient:
    """In-memory Git used for local/dev and tests."""

    def __init__(self) -> None:
        self.calls: List[Dict[str, object]] = []

    async def create_draft_pr(
        self,
        *,
        jira_key: str,
        branch_name: str,
        commit_message: str,
        files: Dict[str, str],
    ) -> GitResult:
        self.calls.append(
            {
                "jira_key": jira_key,
                "branch_name": branch_name,
                "commit_message": commit_message,
                "files": list(files.keys()),
            }
        )
        digest = hashlib.sha256(f"{jira_key}:{commit_message}".encode()).hexdigest()[:12]
        return GitResult(
            branch_name=branch_name,
            commit_sha=f"mock-{digest}",
            pr_url=f"https://github.com/example/agent-nova/pull/0",
        )


class HttpGitHubClient:
    """Live GitHub: blobs → tree → commit → branch ref → draft PR."""

    def __init__(
        self,
        settings: Optional[Settings] = None,
        transport: Optional[httpx.AsyncBaseTransport] = None,
    ) -> None:
        self.settings = settings or get_settings()
        self.transport = transport
        parsed = self.settings.github_owner_repo
        if not parsed:
            raise ValueError("GIT_REPO_URL must be set to owner/repo (or GitHub URL) for GIT_PROVIDER=github")
        if not self.settings.github_token:
            raise ValueError("GITHUB_TOKEN is required when GIT_PROVIDER=github")
        self.owner, self.repo = parsed
        self._api = self.settings.github_api_base_url.rstrip("/")

    async def create_draft_pr(
        self,
        *,
        jira_key: str,
        branch_name: str,
        commit_message: str,
        files: Dict[str, str],
    ) -> GitResult:
        commit_files = {p: c for p, c in files.items() if _is_commit_path(p)}
        if not commit_files:
            raise ValueError("No force-app (or sfdx-project.json) files to commit")

        headers = {
            "Accept": "application/vnd.github+json",
            "Authorization": f"Bearer {self.settings.github_token}",
            "X-GitHub-Api-Version": "2022-11-28",
        }
        default_branch = self.settings.git_default_branch or "main"

        async with httpx.AsyncClient(
            timeout=60.0,
            headers=headers,
            transport=self.transport,
        ) as client:
            ref = await self._get_json(
                client, f"/repos/{self.owner}/{self.repo}/git/ref/heads/{default_branch}"
            )
            base_commit_sha = ref["object"]["sha"]

            base_commit = await self._get_json(
                client, f"/repos/{self.owner}/{self.repo}/git/commits/{base_commit_sha}"
            )
            base_tree_sha = base_commit["tree"]["sha"]

            tree_items = []
            for path, content in sorted(commit_files.items()):
                blob = await self._post_json(
                    client,
                    f"/repos/{self.owner}/{self.repo}/git/blobs",
                    {"content": content, "encoding": "utf-8"},
                )
                tree_items.append(
                    {
                        "path": path,
                        "mode": "100644",
                        "type": "blob",
                        "sha": blob["sha"],
                    }
                )

            tree = await self._post_json(
                client,
                f"/repos/{self.owner}/{self.repo}/git/trees",
                {"base_tree": base_tree_sha, "tree": tree_items},
            )

            commit = await self._post_json(
                client,
                f"/repos/{self.owner}/{self.repo}/git/commits",
                {
                    "message": commit_message,
                    "tree": tree["sha"],
                    "parents": [base_commit_sha],
                },
            )
            commit_sha = commit["sha"]

            await self._post_json(
                client,
                f"/repos/{self.owner}/{self.repo}/git/refs",
                {"ref": f"refs/heads/{branch_name}", "sha": commit_sha},
            )

            pr = await self._post_json(
                client,
                f"/repos/{self.owner}/{self.repo}/pulls",
                {
                    "title": f"[Agent Nova] {jira_key}",
                    "head": branch_name,
                    "base": default_branch,
                    "body": f"Draft PR created by Agent Nova for {jira_key}.\n\n{commit_message}",
                    "draft": True,
                },
            )
            pr_url = pr.get("html_url") or f"https://github.com/{self.owner}/{self.repo}/pull/{pr.get('number')}"

        return GitResult(branch_name=branch_name, commit_sha=commit_sha, pr_url=pr_url)

    async def _get_json(self, client: httpx.AsyncClient, path: str) -> dict:
        resp = await client.get(f"{self._api}{path}")
        resp.raise_for_status()
        return resp.json()

    async def _post_json(self, client: httpx.AsyncClient, path: str, body: dict) -> dict:
        resp = await client.post(f"{self._api}{path}", json=body)
        resp.raise_for_status()
        return resp.json()


def _is_commit_path(path: str) -> bool:
    p = path.replace("\\", "/")
    return p.startswith("force-app/") or p == "sfdx-project.json"


_mock_singleton: Optional[MockGitClient] = None


def get_git_client(settings: Optional[Settings] = None) -> GitClient:
    global _mock_singleton
    settings = settings or get_settings()
    if settings.git_provider == "github":
        return HttpGitHubClient(settings)
    if _mock_singleton is None:
        _mock_singleton = MockGitClient()
    return _mock_singleton


def reset_mock_git() -> MockGitClient:
    global _mock_singleton
    _mock_singleton = MockGitClient()
    return _mock_singleton
