# #region GitServiceGiteaMixin [C:4] [TYPE Module] [SEMANTICS git, gitea, api, remote, connection, http_pool]
# @LAYER Infrastructure
# @BRIEF Gitea API operations for GitService — connection testing, repository CRUD, and pull request creation. Uses shared self._http_client for connection pooling.
# @RELATION CALLED_BY -> [GitService]

from typing import Any
from urllib.parse import quote

from fastapi import HTTPException

from src.core.logger import belief_scope, logger
from src.models.git import GitProvider


# #region GitServiceGiteaMixin [C:3] [TYPE Class]
# @BRIEF Mixin providing Gitea API operations for GitService.
class GitServiceGiteaMixin:
    # region test_connection [TYPE Function]
    # @PURPOSE: Test connection to Git provider using PAT.
    # @PARAM  provider (GitProvider), url (str), pat (str)
    # @PRE  provider is valid; url is a valid HTTP(S) URL; pat is provided.
    # @POST  Returns True if connection to the provider's API succeeds.
    # @RETURN  bool
    async def test_connection(self, provider: GitProvider, url: str, pat: str) -> bool:
        with belief_scope("GitService.test_connection"):
            if ".local" in url or "localhost" in url:
                logger.reason("Local/Offline mode detected for URL", extra={"src": "test_connection"})
                return True
            if not url.startswith(('http://', 'https://')):
                logger.error(f"[test_connection][Coherence:Failed] Invalid URL protocol: {url}")
                return False
            if not pat or not pat.strip():
                logger.error("[test_connection][Coherence:Failed] Git PAT is missing or empty")
                return False
            pat = pat.strip()
            try:
                if provider == GitProvider.GITHUB:
                    headers = {"Authorization": f"token {pat}"}
                    api_url = "https://api.github.com/user" if "github.com" in url else f"{url.rstrip('/')}/api/v3/user"
                    resp = await self._http_client.get(api_url, headers=headers)
                elif provider == GitProvider.GITLAB:
                    headers = {"PRIVATE-TOKEN": pat}
                    api_url = f"{url.rstrip('/')}/api/v4/user"
                    resp = await self._http_client.get(api_url, headers=headers)
                elif provider == GitProvider.GITEA:
                    headers = {"Authorization": f"token {pat}"}
                    api_url = f"{url.rstrip('/')}/api/v1/user"
                    resp = await self._http_client.get(api_url, headers=headers)
                else:
                    return False
                if resp.status_code != 200:
                    logger.error(f"[test_connection][Coherence:Failed] Git connection test failed for {provider} at {api_url}. Status: {resp.status_code}")
                return resp.status_code == 200
            except Exception as e:
                logger.error(f"[test_connection][Coherence:Failed] Error testing git connection: {e}")
                return False
    # endregion test_connection

    # region _gitea_headers [TYPE Function]
    # @PURPOSE: Build Gitea API authorization headers.
    # @PRE  pat is provided.
    # @POST  Returns headers with token auth.
    # @RETURN  Dict[str, str]
    def _gitea_headers(self, pat: str) -> dict[str, str]:
        token = (pat or "").strip()
        if not token:
            raise HTTPException(status_code=400, detail="Git PAT is required for Gitea operations")
        return {
            "Authorization": f"token {token}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }
    # endregion _gitea_headers

    # region _gitea_request [TYPE Function]
    # @PURPOSE: Execute HTTP request against Gitea API with stable error mapping.
    # @PRE  method and endpoint are valid.
    # @POST  Returns decoded JSON payload.
    # @RETURN  Any
    async def _gitea_request(
        self, method: str, server_url: str, pat: str, endpoint: str,
        payload: dict[str, Any] | None = None,
    ) -> Any:
        base_url = self._normalize_git_server_url(server_url)
        url = f"{base_url}/api/v1{endpoint}"
        headers = self._gitea_headers(pat)
        try:
            response = await self._http_client.request(method=method, url=url, headers=headers, json=payload)
        except Exception as e:
            logger.error(f"[gitea_request][Coherence:Failed] Network error: {e}")
            raise HTTPException(status_code=503, detail=f"Gitea API is unavailable: {e!s}")
        if response.status_code >= 400:
            detail = response.text
            try:
                parsed = response.json()
                detail = parsed.get("message") or parsed.get("error") or detail
            except Exception:
                logger.debug("Could not parse error response JSON from Gitea API")
            logger.error(f"[gitea_request][Coherence:Failed] method={method} endpoint={endpoint} status={response.status_code} detail={detail}")
            raise HTTPException(status_code=response.status_code, detail=f"Gitea API error: {detail}")
        if response.status_code == 204:
            return None
        return response.json()
    # endregion _gitea_request

    # region get_gitea_current_user [TYPE Function]
    # @PURPOSE: Resolve current Gitea user for PAT.
    # @PRE  server_url and pat are valid.
    # @POST  Returns current username.
    # @RETURN  str
    async def get_gitea_current_user(self, server_url: str, pat: str) -> str:
        payload = await self._gitea_request("GET", server_url, pat, "/user")
        username = payload.get("login") or payload.get("username")
        if not username:
            raise HTTPException(status_code=500, detail="Failed to resolve Gitea username")
        return str(username)
    # endregion get_gitea_current_user

    # region list_gitea_repositories [TYPE Function]
    # @PURPOSE: List repositories visible to authenticated Gitea user.
    # @PRE  server_url and pat are valid.
    # @POST  Returns repository list from Gitea.
    # @RETURN  List[dict]
    async def list_gitea_repositories(self, server_url: str, pat: str) -> list[dict]:
        payload = await self._gitea_request("GET", server_url, pat, "/user/repos?limit=100&page=1")
        if not isinstance(payload, list):
            return []
        return payload
    # endregion list_gitea_repositories

    # region create_gitea_repository [TYPE Function]
    # @PURPOSE: Create repository in Gitea for authenticated user. Idempotent — returns existing repo if name already taken.
    # @PRE  name is non-empty and PAT has repo creation permission.
    # @POST  Returns created or existing repository payload.
    # @RETURN  dict
    async def create_gitea_repository(
        self, server_url: str, pat: str, name: str, private: bool = True,
        description: str | None = None, auto_init: bool = True, default_branch: str | None = "main",
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {"name": name, "private": bool(private), "auto_init": bool(auto_init)}
        if description:
            payload["description"] = description
        if default_branch:
            payload["default_branch"] = default_branch
        try:
            created = await self._gitea_request("POST", server_url, pat, "/user/repos", payload=payload)
        except HTTPException as exc:
            if exc.status_code == 409:
                logger.reason(
                    f"Repository '{name}' already exists on Gitea, fetching existing repo",
                    extra={"src": "create_gitea_repository"},
                )
                username = await self.get_gitea_current_user(server_url, pat)
                existing = await self._gitea_request("GET", server_url, pat, f"/repos/{username}/{name}")
                if isinstance(existing, dict):
                    return existing
            raise
        if not isinstance(created, dict):
            raise HTTPException(status_code=500, detail="Unexpected Gitea response while creating repository")
        return created
    # endregion create_gitea_repository

    # region delete_gitea_repository [TYPE Function]
    # @PURPOSE: Delete repository in Gitea.
    # @PRE  owner and repo_name are non-empty.
    # @POST  Repository deleted on Gitea server.
    async def delete_gitea_repository(self, server_url: str, pat: str, owner: str, repo_name: str) -> None:
        if not owner or not repo_name:
            raise HTTPException(status_code=400, detail="owner and repo_name are required")
        await self._gitea_request("DELETE", server_url, pat, f"/repos/{owner}/{repo_name}")
    # endregion delete_gitea_repository

    # region _gitea_branch_exists [TYPE Function]
    # @PURPOSE: Check whether a branch exists in Gitea repository.
    # @PRE  owner/repo/branch are non-empty.
    # @POST  Returns True when branch exists, False when 404.
    # @RETURN  bool
    async def _gitea_branch_exists(self, server_url: str, pat: str, owner: str, repo: str, branch: str) -> bool:
        if not owner or not repo or not branch:
            return False
        endpoint = f"/repos/{owner}/{repo}/branches/{quote(branch, safe='')}"
        try:
            await self._gitea_request("GET", server_url, pat, endpoint)
            return True
        except HTTPException as exc:
            if exc.status_code == 404:
                return False
            raise
    # endregion _gitea_branch_exists

    # region _build_gitea_pr_404_detail [TYPE Function]
    # @PURPOSE: Build actionable error detail for Gitea PR 404 responses.
    # @PRE  owner/repo/from_branch/to_branch are provided.
    # @POST  Returns specific branch-missing message when detected.
    # @RETURN  Optional[str]
    async def _build_gitea_pr_404_detail(
        self, server_url: str, pat: str, owner: str, repo: str, from_branch: str, to_branch: str,
    ) -> str | None:
        source_exists = await self._gitea_branch_exists(
            server_url=server_url, pat=pat, owner=owner, repo=repo, branch=from_branch,
        )
        target_exists = await self._gitea_branch_exists(
            server_url=server_url, pat=pat, owner=owner, repo=repo, branch=to_branch,
        )
        if not source_exists:
            return f"Gitea branch not found: source branch '{from_branch}' in {owner}/{repo}"
        if not target_exists:
            return f"Gitea branch not found: target branch '{to_branch}' in {owner}/{repo}"
        return None
    # endregion _build_gitea_pr_404_detail

    # region create_gitea_pull_request [TYPE Function]
    # @PURPOSE: Create pull request in Gitea.
    # @PRE  Config and remote URL are valid.
    # @POST  Returns normalized PR metadata.
    # @RETURN  Dict[str, Any]
    async def create_gitea_pull_request(
        self, server_url: str, pat: str, remote_url: str, from_branch: str, to_branch: str,
        title: str, description: str | None = None,
    ) -> dict[str, Any]:
        identity = self._parse_remote_repo_identity(remote_url)
        req_payload = {"title": title, "head": from_branch, "base": to_branch, "body": description or ""}
        endpoint = f"/repos/{identity['owner']}/{identity['repo']}/pulls"
        active_server_url = server_url
        try:
            data = await self._gitea_request("POST", active_server_url, pat, endpoint, payload=req_payload)
        except HTTPException as exc:
            fallback_url = self._derive_server_url_from_remote(remote_url)
            normalized_primary = self._normalize_git_server_url(server_url)
            should_retry_with_fallback = (
                exc.status_code == 404 and fallback_url and fallback_url != normalized_primary
            )
            if should_retry_with_fallback:
                logger.explore(
                    f"Primary Gitea URL not found, retrying with remote host: {fallback_url}",
                    extra={"src": "create_gitea_pull_request"},
                )
                active_server_url = fallback_url
                try:
                    data = await self._gitea_request("POST", active_server_url, pat, endpoint, payload=req_payload)
                except HTTPException as retry_exc:
                    if retry_exc.status_code == 404:
                        branch_detail = await self._build_gitea_pr_404_detail(
                            server_url=active_server_url, pat=pat,
                            owner=identity["owner"], repo=identity["repo"],
                            from_branch=from_branch, to_branch=to_branch,
                        )
                        if branch_detail:
                            raise HTTPException(status_code=400, detail=branch_detail)
                    raise
            else:
                if exc.status_code == 404:
                    branch_detail = await self._build_gitea_pr_404_detail(
                        server_url=active_server_url, pat=pat,
                        owner=identity["owner"], repo=identity["repo"],
                        from_branch=from_branch, to_branch=to_branch,
                    )
                    if branch_detail:
                        raise HTTPException(status_code=400, detail=branch_detail)
                raise
        if not isinstance(data, dict):
            raise HTTPException(status_code=500, detail="Unexpected Gitea response while creating pull request")
        return {
            "id": data.get("number") or data.get("id"),
            "url": data.get("html_url") or data.get("url"),
            "status": data.get("state") or "open",
        }
    # endregion create_gitea_pull_request
# #endregion GitServiceGiteaMixin
# #endregion GitServiceGiteaMixin
