# retoor <retoor@molodetz.nl>

import logging

import httpx

from devplacepy import stealth
from devplacepy.services.gitea.config import (
    HTTP_TIMEOUT_SECONDS,
    GiteaConfig,
    gitea_config,
)

logger = logging.getLogger(__name__)

ISSUE_TYPE = "issues"
STATE_OPEN = "open"
STATE_CLOSED = "closed"
STATE_ALL = "all"
VALID_STATES = (STATE_OPEN, STATE_CLOSED, STATE_ALL)


class GiteaError(Exception):
    def __init__(self, message: str, status: int = 0):
        super().__init__(message)
        self.status = status
        self.message = message


class GiteaClient:
    def __init__(self, config: GiteaConfig | None = None):
        self.config = config or gitea_config()

    def _headers(self, json_body: bool = True) -> dict:
        headers = {
            "Authorization": f"token {self.config.token}",
            "Accept": "application/json",
        }
        if json_body:
            headers["Content-Type"] = "application/json"
        return headers

    def _require_config(self) -> None:
        if not self.config.is_configured:
            raise GiteaError("Gitea integration is not configured", status=503)

    async def _request(
        self, method: str, url: str, json_body: bool = True, **kwargs
    ) -> httpx.Response:
        self._require_config()
        logger.debug("Gitea %s %s", method, url)
        try:
            async with stealth.stealth_async_client(timeout=HTTP_TIMEOUT_SECONDS) as client:
                response = await client.request(
                    method, url, headers=self._headers(json_body), **kwargs
                )
        except httpx.HTTPError as exc:
            logger.warning("Gitea request failed: %s", exc)
            raise GiteaError(f"Gitea request failed: {exc}") from exc
        if response.status_code >= 400:
            logger.warning(
                "Gitea %s %s returned %s: %s",
                method,
                url,
                response.status_code,
                response.text[:200],
            )
            raise GiteaError(
                f"Gitea returned {response.status_code}", status=response.status_code
            )
        return response

    async def create_issue(self, title: str, body: str) -> dict:
        response = await self._request(
            "POST",
            f"{self.config.repo_base}/issues",
            json={"title": title, "body": body},
        )
        return response.json()

    async def list_issues(
        self, state: str = STATE_OPEN, page: int = 1, limit: int = 20
    ) -> tuple[list[dict], int]:
        if state not in VALID_STATES:
            state = STATE_OPEN
        response = await self._request(
            "GET",
            f"{self.config.repo_base}/issues",
            params={
                "type": ISSUE_TYPE,
                "state": state,
                "page": max(1, page),
                "limit": max(1, min(limit, 50)),
            },
        )
        issues = [item for item in response.json() if not item.get("pull_request")]
        total = self._total_count(response, len(issues))
        return issues, total

    async def get_issue(self, number: int) -> dict:
        response = await self._request(
            "GET", f"{self.config.repo_base}/issues/{number}"
        )
        return response.json()

    async def list_comments(self, number: int) -> list[dict]:
        response = await self._request(
            "GET", f"{self.config.repo_base}/issues/{number}/comments"
        )
        return response.json()

    async def create_comment(self, number: int, body: str) -> dict:
        response = await self._request(
            "POST",
            f"{self.config.repo_base}/issues/{number}/comments",
            json={"body": body},
        )
        return response.json()

    async def set_state(self, number: int, state: str) -> dict:
        if state not in (STATE_OPEN, STATE_CLOSED):
            raise GiteaError(f"invalid state: {state}", status=400)
        response = await self._request(
            "PATCH",
            f"{self.config.repo_base}/issues/{number}",
            json={"state": state},
        )
        return response.json()

    async def list_issue_assets(self, number: int) -> list[dict]:
        response = await self._request(
            "GET", f"{self.config.repo_base}/issues/{number}/assets"
        )
        return response.json()

    async def create_issue_asset(
        self, number: int, filename: str, data: bytes, mime: str
    ) -> dict:
        response = await self._request(
            "POST",
            f"{self.config.repo_base}/issues/{number}/assets",
            json_body=False,
            files={"attachment": (filename, data, mime or "application/octet-stream")},
        )
        return response.json()

    async def delete_issue_asset(self, number: int, attachment_id: int) -> None:
        await self._request(
            "DELETE",
            f"{self.config.repo_base}/issues/{number}/assets/{attachment_id}",
        )

    async def list_comment_assets(self, comment_id: int) -> list[dict]:
        response = await self._request(
            "GET", f"{self.config.repo_base}/issues/comments/{comment_id}/assets"
        )
        return response.json()

    async def create_comment_asset(
        self, comment_id: int, filename: str, data: bytes, mime: str
    ) -> dict:
        response = await self._request(
            "POST",
            f"{self.config.repo_base}/issues/comments/{comment_id}/assets",
            json_body=False,
            files={"attachment": (filename, data, mime or "application/octet-stream")},
        )
        return response.json()

    async def delete_comment_asset(self, comment_id: int, attachment_id: int) -> None:
        await self._request(
            "DELETE",
            f"{self.config.repo_base}/issues/comments/{comment_id}/assets/{attachment_id}",
        )

    @staticmethod
    def _total_count(response: httpx.Response, fallback: int) -> int:
        raw = response.headers.get("X-Total-Count", "")
        try:
            return int(raw)
        except (TypeError, ValueError):
            return fallback
