"""MongolChat payment client implementations (sync and async)."""

import re
from dataclasses import asdict
from typing import Any, Dict

import httpx

from .config import validate_config
from .errors import MongolChatError
from .types import (
    MchatGenerateQrInput,
    MchatOnlineQrCheckResponse,
    MchatOnlineQrGenerateResponse,
    MongolChatConfig,
)


class MongolChatClient:
    """MongolChat payment client (synchronous).

    Generates online QR codes and checks their payment status via the
    MongolChat worker API.

    Example::

        client = MongolChatClient(MongolChatConfig(
            endpoint="https://api.mongolchat.mn",
            api_key="my-api-key",
            worker_key="my-worker-key",
            app_secret="my-app-secret",
            branch_no="001",
        ))

        qr = client.generate_qr(MchatGenerateQrInput(
            amount=5000,
            products=[MchatProduct(product_name="Coffee", quantity="1", price=5000, tag="food")],
            title="Coffee Shop",
            sub_title="Order #123",
            noat="123456",
            nhat="654321",
            ttd="789",
            reference_number="REF-001",
            expire_time="2026-12-31T23:59:59",
        ))
        print(qr.qr)

        status = client.check_qr(qr.qr)
        print(status.status)
    """

    def __init__(self, config: MongolChatConfig) -> None:
        validate_config(config)
        self._endpoint = re.sub(r"/+$", "", config.endpoint)
        self._api_key = config.api_key
        self._worker_key = config.worker_key
        self._app_secret = config.app_secret
        self._branch_no = config.branch_no
        self._client = httpx.Client()

    # ── Public API ──

    def generate_qr(
        self, input: MchatGenerateQrInput
    ) -> MchatOnlineQrGenerateResponse:
        """Generate a QR code for an online payment.

        Args:
            input: QR generation parameters.

        Returns:
            The generated QR code response.

        Raises:
            MongolChatError: If the API returns a non-1000 code or a network error occurs.
        """
        body: Dict[str, Any] = {
            "amount": input.amount,
            "products": [asdict(p) for p in input.products],
            "title": input.title,
            "sub_title": input.sub_title,
            "noat": input.noat,
            "nhat": input.nhat,
            "ttd": input.ttd,
            "reference_number": input.reference_number,
            "expire_time": input.expire_time,
        }

        if input.branch_id:
            body["branch_id"] = input.branch_id

        data = self._post("/worker/onlineqr/generate", body)

        if data.get("code") != 1000:
            raise MongolChatError(
                f"MongolChat error ({data.get('code')}): {data.get('message')}",
                code=data.get("code"),
                response=data,
            )

        return MchatOnlineQrGenerateResponse(
            qr=data.get("qr", ""),
            code=data.get("code", 0),
            message=data.get("message", ""),
        )

    def check_qr(self, qr: str) -> MchatOnlineQrCheckResponse:
        """Check the payment status of a QR code.

        Args:
            qr: The QR code string returned from generate_qr.

        Returns:
            The QR status response.

        Raises:
            MongolChatError: If the API returns a non-1000 code or a network error occurs.
        """
        data = self._post("/worker/onlineqr/status", {"qr": qr})

        if data.get("code") != 1000:
            raise MongolChatError(
                f"MongolChat error ({data.get('code')}): {data.get('message')}",
                code=data.get("code"),
                response=data,
            )

        return MchatOnlineQrCheckResponse(
            status=data.get("status", ""),
            code=data.get("code", 0),
            message=data.get("message", ""),
            id=data.get("id", ""),
            who_paid=data.get("who_paid", ""),
            user_ref_id=data.get("user_ref_id", ""),
        )

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> "MongolChatClient":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    # ── Private helpers ──

    def _headers(self) -> Dict[str, str]:
        return {
            "api-key": self._api_key,
            "Authorization": f"WorkerKey {self._worker_key}",
            "Content-Type": "application/json",
        }

    def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        """Send a POST request to the MongolChat API."""
        url = f"{self._endpoint}{path}"

        try:
            res = self._client.post(url, json=body, headers=self._headers())
        except httpx.HTTPError as exc:
            raise MongolChatError(
                f"Network error calling {path}: {exc}"
            ) from exc

        try:
            json_data: Dict[str, Any] = res.json()
        except Exception:
            raise MongolChatError(
                f"Invalid JSON response from {path}",
                response=res.text,
            )

        return json_data


class AsyncMongolChatClient:
    """MongolChat payment client (asynchronous).

    Generates online QR codes and checks their payment status via the
    MongolChat worker API.
    """

    def __init__(self, config: MongolChatConfig) -> None:
        validate_config(config)
        self._endpoint = re.sub(r"/+$", "", config.endpoint)
        self._api_key = config.api_key
        self._worker_key = config.worker_key
        self._app_secret = config.app_secret
        self._branch_no = config.branch_no
        self._client = httpx.AsyncClient()

    # ── Public API ──

    async def generate_qr(
        self, input: MchatGenerateQrInput
    ) -> MchatOnlineQrGenerateResponse:
        """Generate a QR code for an online payment.

        Args:
            input: QR generation parameters.

        Returns:
            The generated QR code response.

        Raises:
            MongolChatError: If the API returns a non-1000 code or a network error occurs.
        """
        body: Dict[str, Any] = {
            "amount": input.amount,
            "products": [asdict(p) for p in input.products],
            "title": input.title,
            "sub_title": input.sub_title,
            "noat": input.noat,
            "nhat": input.nhat,
            "ttd": input.ttd,
            "reference_number": input.reference_number,
            "expire_time": input.expire_time,
        }

        if input.branch_id:
            body["branch_id"] = input.branch_id

        data = await self._post("/worker/onlineqr/generate", body)

        if data.get("code") != 1000:
            raise MongolChatError(
                f"MongolChat error ({data.get('code')}): {data.get('message')}",
                code=data.get("code"),
                response=data,
            )

        return MchatOnlineQrGenerateResponse(
            qr=data.get("qr", ""),
            code=data.get("code", 0),
            message=data.get("message", ""),
        )

    async def check_qr(self, qr: str) -> MchatOnlineQrCheckResponse:
        """Check the payment status of a QR code.

        Args:
            qr: The QR code string returned from generate_qr.

        Returns:
            The QR status response.

        Raises:
            MongolChatError: If the API returns a non-1000 code or a network error occurs.
        """
        data = await self._post("/worker/onlineqr/status", {"qr": qr})

        if data.get("code") != 1000:
            raise MongolChatError(
                f"MongolChat error ({data.get('code')}): {data.get('message')}",
                code=data.get("code"),
                response=data,
            )

        return MchatOnlineQrCheckResponse(
            status=data.get("status", ""),
            code=data.get("code", 0),
            message=data.get("message", ""),
            id=data.get("id", ""),
            who_paid=data.get("who_paid", ""),
            user_ref_id=data.get("user_ref_id", ""),
        )

    async def close(self) -> None:
        """Close the underlying HTTP client."""
        await self._client.aclose()

    async def __aenter__(self) -> "AsyncMongolChatClient":
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    # ── Private helpers ──

    def _headers(self) -> Dict[str, str]:
        return {
            "api-key": self._api_key,
            "Authorization": f"WorkerKey {self._worker_key}",
            "Content-Type": "application/json",
        }

    async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        """Send a POST request to the MongolChat API."""
        url = f"{self._endpoint}{path}"

        try:
            res = await self._client.post(url, json=body, headers=self._headers())
        except httpx.HTTPError as exc:
            raise MongolChatError(
                f"Network error calling {path}: {exc}"
            ) from exc

        try:
            json_data: Dict[str, Any] = res.json()
        except Exception:
            raise MongolChatError(
                f"Invalid JSON response from {path}",
                response=res.text,
            )

        return json_data
