"""GitLab OAuth/API client for customer-owned repositories."""
from __future__ import annotations

import time
import urllib.parse
from typing import Any, Optional

import httpx

from src.config import Settings, get_settings


class GitLabUserClient:
    def __init__(self, *, access_token: Optional[str] = None, settings: Optional[Settings] = None):
        self._settings = settings or get_settings()
        self._access_token = (access_token or "").strip()
        self._base_url = (self._settings.gitlab_user_oauth_base_url or "https://gitlab.com").rstrip("/")

    @property
    def base_url(self) -> str:
        return self._base_url

    @property
    def api_url(self) -> str:
        return f"{self._base_url}/api/v4"

    def authorization_url(self, *, redirect_uri: str, state: str) -> str:
        client_id = (self._settings.gitlab_user_oauth_client_id or "").strip()
        params = {
            "client_id": client_id,
            "redirect_uri": redirect_uri,
            "response_type": "code",
            "state": state,
            "scope": "read_user read_repository write_repository api",
        }
        return f"{self._base_url}/oauth/authorize?{urllib.parse.urlencode(params)}"

    async def exchange_code(self, *, code: str, redirect_uri: str) -> dict[str, Any]:
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.post(
                f"{self._base_url}/oauth/token",
                data={
                    "client_id": self._settings.gitlab_user_oauth_client_id,
                    "client_secret": self._settings.gitlab_user_oauth_client_secret,
                    "code": code,
                    "grant_type": "authorization_code",
                    "redirect_uri": redirect_uri,
                },
            )
        r.raise_for_status()
        return r.json()

    async def get_current_user(self) -> dict[str, Any]:
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.get(f"{self.api_url}/user", headers=self._headers())
        r.raise_for_status()
        return r.json()

    async def list_projects(self) -> list[dict[str, Any]]:
        out: list[dict[str, Any]] = []
        url: Optional[str] = (
            f"{self.api_url}/projects?"
            + urllib.parse.urlencode(
                {
                    "membership": "true",
                    "simple": "true",
                    "per_page": "100",
                    "order_by": "last_activity_at",
                    "sort": "desc",
                }
            )
        )
        async with httpx.AsyncClient(timeout=120.0) as client:
            while url:
                r = await client.get(url, headers=self._headers())
                r.raise_for_status()
                out.extend(r.json() or [])
                url = r.links.get("next", {}).get("url")
        return out

    async def get_project_by_path(self, full_path: str) -> dict[str, Any]:
        encoded = urllib.parse.quote(full_path.strip(), safe="")
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.get(f"{self.api_url}/projects/{encoded}", headers=self._headers())
        r.raise_for_status()
        return r.json()

    def authenticated_clone_url(self, repo_full_path: str) -> str:
        path = repo_full_path.strip().strip("/")
        host = urllib.parse.urlparse(self._base_url).netloc
        return f"https://oauth2:{urllib.parse.quote(self._access_token, safe='')}@{host}/{path}.git"

    def token_expiry_epoch(self, token_payload: dict[str, Any]) -> Optional[float]:
        expires_in = token_payload.get("expires_in")
        try:
            return time.time() + int(expires_in)
        except Exception:
            return None

    async def register_project_webhook(
        self, full_path: str, webhook_url: str, secret: str
    ) -> str:
        """Register a push webhook on a GitLab project. Returns the webhook ID as a string."""
        encoded = urllib.parse.quote(full_path.strip(), safe="")
        async with httpx.AsyncClient(timeout=30.0) as client:
            r = await client.post(
                f"{self.api_url}/projects/{encoded}/hooks",
                headers=self._headers(),
                json={
                    "url": webhook_url,
                    "push_events": True,
                    "tag_push_events": False,
                    "merge_requests_events": False,
                    "token": secret,
                    "enable_ssl_verification": True,
                },
            )
        r.raise_for_status()
        return str(r.json().get("id", ""))

    async def delete_project_webhook(self, full_path: str, webhook_id: str) -> None:
        """Delete a previously registered webhook. Silently ignores 404 (already gone)."""
        encoded = urllib.parse.quote(full_path.strip(), safe="")
        async with httpx.AsyncClient(timeout=30.0) as client:
            r = await client.delete(
                f"{self.api_url}/projects/{encoded}/hooks/{webhook_id}",
                headers=self._headers(),
            )
        if r.status_code == 404:
            return
        r.raise_for_status()

    def _headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self._access_token}"}
