"""Async WHM/cPanel API client.

Wraps WHM JSON API v1 (port 2087/2083 or configured) with:
  * ``Authorization: whm <user>:<token>`` bearer auth
  * configurable TLS verification
  * masked logging into ``cpanel_api_logs``

The client exposes a small surface: the raw ``call`` primitive plus a
few high-level helpers used by the provisioning service and client-area
quick actions.  Anything not wrapped here can still be invoked through
:meth:`CpanelApi.call`.
"""
from __future__ import annotations

import logging
import re
import time
from typing import Any, Dict, Optional

import httpx
from sqlalchemy.ext.asyncio import AsyncSession

from core.mask_crypto import decrypt_text
from models.cpanel import CpanelApiLog, CpanelServer

logger = logging.getLogger(__name__)


_SECRET_PATTERNS = [
    re.compile(r"(authorization\s*:\s*whm\s+[^:]+:)[^\s\"']+", re.IGNORECASE),
    re.compile(r"(api[_-]?token\s*[=:]\s*)[^\s&\"']+", re.IGNORECASE),
    re.compile(r"(password\s*[=:]\s*)[^\s&\"']+", re.IGNORECASE),
    re.compile(r"(pass\s*[=:]\s*)[^\s&\"']+", re.IGNORECASE),
]


def _mask(text: Optional[str], limit: int = 4000) -> Optional[str]:
    if not text:
        return text
    masked = text
    for pat in _SECRET_PATTERNS:
        masked = pat.sub(r"\1***", masked)
    return masked[:limit]


class CpanelApiError(RuntimeError):
    """Raised when WHM returns a non-success response."""


class CpanelApi:
    """Lightweight async wrapper around one WHM endpoint."""

    def __init__(self, server: CpanelServer, db: Optional[AsyncSession] = None) -> None:
        self.server = server
        self.db = db
        self._token = decrypt_text(server.encrypted_api_token) if server.encrypted_api_token else ""

    # ------------------------------------------------------------------
    # public
    # ------------------------------------------------------------------
    @property
    def base_url(self) -> str:
        return f"https://{self.server.hostname}:{self.server.port}"

    async def call(
        self,
        endpoint: str,
        params: Optional[Dict[str, Any]] = None,
        *,
        method: str = "GET",
        account_id: Optional[int] = None,
        timeout: float = 30.0,
    ) -> Dict[str, Any]:
        """Invoke a WHM JSON API endpoint and return the parsed JSON body.

        ``endpoint`` is the action name (e.g. ``createacct``). The full
        URL is assembled as ``/json-api/<endpoint>?api.version=1&...``.
        """
        url = f"{self.base_url}/json-api/{endpoint}"
        query = {"api.version": "1"}
        if params:
            query.update({k: v for k, v in params.items() if v is not None})

        headers = {
            "Authorization": f"whm {self.server.username}:{self._token}",
            "Accept": "application/json",
        }

        started = time.perf_counter()
        status = "ok"
        http_status: Optional[int] = None
        error_message: Optional[str] = None
        body: Dict[str, Any] = {}
        response_text = ""

        try:
            async with httpx.AsyncClient(verify=self.server.verify_tls, timeout=timeout) as client:
                if method.upper() == "GET":
                    resp = await client.get(url, params=query, headers=headers)
                else:
                    resp = await client.request(method.upper(), url, params=query, headers=headers)
                http_status = resp.status_code
                response_text = resp.text or ""
                try:
                    body = resp.json() if response_text else {}
                except Exception:  # noqa: BLE001
                    body = {"_raw": response_text[:2000]}

                if resp.status_code >= 400:
                    status = "failed"
                    error_message = f"HTTP {resp.status_code}: {response_text[:300]}"
                else:
                    # WHM sometimes wraps failures inside metadata.result=0.
                    meta = (body.get("metadata") or {}) if isinstance(body, dict) else {}
                    if isinstance(meta, dict) and meta.get("result") == 0:
                        status = "failed"
                        error_message = str(meta.get("reason") or "WHM returned result=0")[:500]
        except httpx.HTTPError as exc:
            status = "failed"
            error_message = f"transport_error: {exc}"[:500]
        except Exception as exc:  # noqa: BLE001
            status = "failed"
            error_message = f"unexpected: {exc}"[:500]

        duration_ms = int((time.perf_counter() - started) * 1000)
        await self._log(
            endpoint=endpoint,
            method=method,
            status=status,
            http_status=http_status,
            duration_ms=duration_ms,
            request_preview=_mask(str(query)),
            response_preview=_mask(response_text),
            error_message=error_message,
            account_id=account_id,
        )

        if status != "ok":
            raise CpanelApiError(error_message or "cPanel API call failed")

        return body if isinstance(body, dict) else {"_raw": body}

    # ------------------------------------------------------------------
    # High-level helpers (minimal MVP coverage)
    # ------------------------------------------------------------------
    async def version(self) -> Dict[str, Any]:
        return await self.call("version")

    async def list_packages(self) -> Dict[str, Any]:
        return await self.call("listpkgs")

    async def list_accounts(self) -> Dict[str, Any]:
        return await self.call("listaccts")

    async def account_summary(self, user: str) -> Dict[str, Any]:
        return await self.call("accountsummary", {"user": user})

    async def create_account(
        self,
        *,
        username: str,
        domain: str,
        password: str,
        package: str,
        contact_email: Optional[str] = None,
        account_id: Optional[int] = None,
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {
            "username": username,
            "domain": domain,
            "password": password,
            "plan": package,
        }
        if contact_email:
            params["contactemail"] = contact_email
        return await self.call("createacct", params, account_id=account_id)

    async def suspend_account(
        self, username: str, reason: Optional[str] = None, *, account_id: Optional[int] = None
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {"user": username}
        if reason:
            params["reason"] = reason
        return await self.call("suspendacct", params, account_id=account_id)

    async def unsuspend_account(self, username: str, *, account_id: Optional[int] = None) -> Dict[str, Any]:
        return await self.call("unsuspendacct", {"user": username}, account_id=account_id)

    async def terminate_account(self, username: str, *, account_id: Optional[int] = None) -> Dict[str, Any]:
        return await self.call("removeacct", {"user": username}, account_id=account_id)

    async def change_package(
        self, username: str, package: str, *, account_id: Optional[int] = None
    ) -> Dict[str, Any]:
        return await self.call("changepackage", {"user": username, "pkg": package}, account_id=account_id)

    async def create_user_session(
        self, username: str, service: str = "cpaneld", *, account_id: Optional[int] = None
    ) -> Dict[str, Any]:
        """Return a one-click login URL (WHM ``create_user_session``)."""
        return await self.call(
            "create_user_session",
            {"user": username, "service": service},
            account_id=account_id,
        )

    # ------------------------------------------------------------------
    # UAPI proxy (for client-area quick actions)
    # ------------------------------------------------------------------
    async def uapi(
        self,
        *,
        cpanel_user: str,
        module: str,
        function: str,
        params: Optional[Dict[str, Any]] = None,
        account_id: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Invoke a UAPI function on behalf of a cPanel user via WHM's ``cpanel`` proxy."""
        full_params: Dict[str, Any] = {
            "cpanel_jsonapi_user": cpanel_user,
            "cpanel_jsonapi_apiversion": 3,
            "cpanel_jsonapi_module": module,
            "cpanel_jsonapi_func": function,
        }
        if params:
            # Prefix each uapi argument
            for key, value in params.items():
                full_params[key] = value
        return await self.call("cpanel", full_params, account_id=account_id)

    # ------------------------------------------------------------------
    # internal
    # ------------------------------------------------------------------
    async def _log(
        self,
        *,
        endpoint: str,
        method: str,
        status: str,
        http_status: Optional[int],
        duration_ms: int,
        request_preview: Optional[str],
        response_preview: Optional[str],
        error_message: Optional[str],
        account_id: Optional[int],
    ) -> None:
        if self.db is None:
            return
        try:
            entry = CpanelApiLog(
                server_id=self.server.id,
                account_id=account_id,
                endpoint=endpoint,
                method=method,
                status=status,
                http_status=http_status,
                duration_ms=duration_ms,
                request_preview=request_preview,
                response_preview=response_preview,
                error_message=error_message,
            )
            self.db.add(entry)
            await self.db.commit()
        except Exception:  # noqa: BLE001
            logger.exception("[cpanel] failed to persist api log")
            try:
                await self.db.rollback()
            except Exception:  # noqa: BLE001
                pass


async def probe_server(server: CpanelServer, db: Optional[AsyncSession] = None) -> Dict[str, Any]:
    """Perform a lightweight ``version`` call to test connectivity."""
    api = CpanelApi(server, db=db)
    try:
        data = await api.version()
        version = None
        if isinstance(data, dict):
            version = (
                (data.get("data") or {}).get("version")
                if isinstance(data.get("data"), dict)
                else data.get("version")
            )
        return {"ok": True, "version": version, "message": "Connection OK"}
    except CpanelApiError as exc:
        return {"ok": False, "message": str(exc)}