"""GitHub REST API client for creating branches, commits, and PRs.

No local git binary dependency. Uses httpx for HTTP calls.
"""

from __future__ import annotations

import re
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

import httpx
from pydantic import BaseModel, ConfigDict

from complyform.core.errors import (
    PRBranchExistsError,
    PRCreationError,
    PRRepoDetectionError,
    PRTokenInvalidError,
)

_BASE_URL = "https://api.github.com"
_TIMEOUT = 30.0


class PatchFile(BaseModel):
    """A file to commit in the PR."""

    model_config = ConfigDict(strict=True)

    path: str
    content: str


class PRResult(BaseModel):
    """Result of PR creation."""

    model_config = ConfigDict(strict=True)

    url: str
    branch: str
    base: str
    draft: bool
    files_committed: int
    commit_sha: str


class GitHubPRClient:
    """Creates branches, commits patches, and opens PRs via GitHub REST API."""

    def __init__(self, token: str, repo_owner: str, repo_name: str) -> None:
        self._owner = repo_owner
        self._repo = repo_name
        self._client = httpx.Client(
            base_url=_BASE_URL,
            headers={
                "Authorization": f"Bearer {token}",
                "Accept": "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
            timeout=_TIMEOUT,
        )

    def validate_token(self) -> None:
        """GET /user — validate token."""
        resp = self._client.get("/user")
        if resp.status_code in (401, 403):
            raise PRTokenInvalidError(resp.status_code, resp.text[:200])
        resp.raise_for_status()

    def get_default_branch(self) -> str:
        """GET /repos/{owner}/{repo} — return default_branch."""
        resp = self._client.get(f"/repos/{self._owner}/{self._repo}")
        if resp.status_code == 404:
            raise PRRepoDetectionError()
        resp.raise_for_status()
        return str(resp.json()["default_branch"])

    def get_branch_sha(self, branch: str) -> str:
        """GET /repos/{owner}/{repo}/git/ref/heads/{branch}."""
        resp = self._client.get(
            f"/repos/{self._owner}/{self._repo}/git/ref/heads/{branch}"
        )
        resp.raise_for_status()
        return str(resp.json()["object"]["sha"])

    def create_branch(self, branch_name: str, from_sha: str) -> None:
        """POST /repos/{owner}/{repo}/git/refs."""
        resp = self._client.post(
            f"/repos/{self._owner}/{self._repo}/git/refs",
            json={
                "ref": f"refs/heads/{branch_name}",
                "sha": from_sha,
            },
        )
        if resp.status_code == 422:
            raise PRBranchExistsError(branch_name)
        resp.raise_for_status()

    def create_tree(
        self,
        base_sha: str,
        patch_files: list[PatchFile],
    ) -> str:
        """POST /repos/{owner}/{repo}/git/trees."""
        tree_items = [
            {
                "path": pf.path,
                "mode": "100644",
                "type": "blob",
                "content": pf.content,
            }
            for pf in patch_files
        ]
        resp = self._client.post(
            f"/repos/{self._owner}/{self._repo}/git/trees",
            json={
                "base_tree": base_sha,
                "tree": tree_items,
            },
        )
        resp.raise_for_status()
        return str(resp.json()["sha"])

    def create_commit(
        self,
        tree_sha: str,
        parent_sha: str,
        message: str,
    ) -> str:
        """POST /repos/{owner}/{repo}/git/commits."""
        resp = self._client.post(
            f"/repos/{self._owner}/{self._repo}/git/commits",
            json={
                "message": message,
                "tree": tree_sha,
                "parents": [parent_sha],
            },
        )
        resp.raise_for_status()
        return str(resp.json()["sha"])

    def update_branch_ref(self, branch_name: str, commit_sha: str) -> None:
        """PATCH /repos/{owner}/{repo}/git/refs/heads/{branch}."""
        resp = self._client.patch(
            f"/repos/{self._owner}/{self._repo}/git/refs/heads/{branch_name}",
            json={"sha": commit_sha, "force": False},
        )
        resp.raise_for_status()

    def create_pull_request(
        self,
        *,
        title: str,
        body: str,
        head: str,
        base: str,
        draft: bool = False,
    ) -> PRResult:
        """POST /repos/{owner}/{repo}/pulls — open PR."""
        resp = self._client.post(
            f"/repos/{self._owner}/{self._repo}/pulls",
            json={
                "title": title,
                "body": body,
                "head": head,
                "base": base,
                "draft": draft,
            },
        )
        if resp.status_code != 201:
            raise PRCreationError(resp.status_code, resp.text[:200])
        data: dict[str, Any] = resp.json()
        return PRResult(
            url=data["html_url"],
            branch=head,
            base=base,
            draft=draft,
            files_committed=0,
            commit_sha=data["head"]["sha"],
        )


def detect_github_repo() -> tuple[str, str]:
    """Detect owner/repo from .git/config. No git binary."""
    cwd = Path.cwd()
    for parent in [cwd, *list(cwd.parents)[:5]]:
        git_config = parent / ".git" / "config"
        if git_config.exists():
            return _parse_git_config(git_config.read_text(encoding="utf-8"))
    raise PRRepoDetectionError()


def _parse_git_config(content: str) -> tuple[str, str]:
    """Parse owner/repo from git config remote origin URL."""
    in_origin = False
    for line in content.splitlines():
        stripped = line.strip()
        if stripped == '[remote "origin"]':
            in_origin = True
            continue
        if in_origin and stripped.startswith("["):
            break
        if in_origin and stripped.startswith("url = "):
            url = stripped.removeprefix("url = ").strip()
            return _parse_github_url(url)
    raise PRRepoDetectionError()


def _parse_github_url(url: str) -> tuple[str, str]:
    """Extract owner/repo from GitHub URL."""
    # HTTPS: https://github.com/owner/repo.git
    https_match = re.match(
        r"https://github\.com/([^/]+)/([^/]+?)(?:\.git)?$",
        url,
    )
    if https_match:
        return https_match.group(1), https_match.group(2)

    # SSH: git@github.com:owner/repo.git
    ssh_match = re.match(
        r"git@github\.com:([^/]+)/([^/]+?)(?:\.git)?$",
        url,
    )
    if ssh_match:
        return ssh_match.group(1), ssh_match.group(2)

    raise PRRepoDetectionError()


def default_branch_name(framework: str) -> str:
    """Generate default branch name."""
    ts = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
    return f"complyform/{framework}-{ts}"


def build_pr_body(
    *,
    framework: str,
    control_count: int,
    cloud: str,
    patches: list[tuple[str, str, str]],
    version: str,
) -> str:
    """Build PR body markdown.

    patches: list of (path, control_id, title) tuples.
    """
    patch_lines = "\n".join(
        f"- `{path}`: {cid} — {title}" for path, cid, title in patches
    )
    return (
        "## ComplyForm Compliance Remediation\n\n"
        f"**Framework:** {framework}\n"
        f"**Controls remediated:** {control_count}\n"
        f"**Cloud:** {cloud}\n\n"
        "### Patches\n\n"
        f"{patch_lines}\n\n"
        "---\n\n"
        f"*Generated by [ComplyForm]"
        f"(https://complyform.dev) v{version}*\n"
    )


def build_commit_message(framework: str, control_count: int) -> str:
    """Build commit message."""
    return f"fix(compliance): {framework} remediation — {control_count} controls"
