"""
Cygn Enterprise SDK for Python

Usage:
    from cygn import CygnEnterprise

    cygn = CygnEnterprise(license_key="cygn_ent_xxxx")
    # Or with Cygn-Token (Ed25519 cryptographic identity):
    # cygn = CygnEnterprise(cygn_token="eyJhbGci...")

    # Sign an image with "Captured by YourCompany" provenance
    result = cygn.sign_image(
        image=base64_image_data,
        provenance="captured",
        app_name="MyApp Camera",
    )

    # Deepfake detection + signing in one call
    bundle = cygn.provenance_bundle(image=base64_image_data)
"""

from __future__ import annotations

import json
from typing import Any, Optional
from urllib.request import Request, urlopen
from urllib.error import HTTPError

from .types import (
    DetectDeepfakeResult,
    DetectVideoDeepfakeResult,
    SignImageResult,
    SignDocumentResult,
    ProvenanceBundleResult,
    SonicEmbedResult,
    SonicDetectResult,
    SonicRegisterResult,
    SonicLookupResult,
    VoiceEnrollResult,
    VoiceVerifyResult,
    UsageSummary,
    DeepfakeSignal,
    DeepfakeAnalysis,
    EnterpriseInfo,
)

__version__ = "1.0.1"
__all__ = ["CygnEnterprise", "CygnAPIError"]

DEFAULT_BASE_URL = "https://cygn.me/api/v1"
DEFAULT_TIMEOUT = 30


class CygnAPIError(Exception):
    """Raised when the Cygn API returns an error response."""

    def __init__(self, status: int, error: str, balance: Optional[int] = None, cost: Optional[int] = None):
        self.status = status
        self.error = error
        self.balance = balance
        self.cost = cost
        super().__init__(f"[{status}] {error}")

    @property
    def code(self) -> str:
        codes = {
            400: "VALIDATION_ERROR",
            401: "UNAUTHORIZED",
            402: "INSUFFICIENT_BALANCE",
            403: "FORBIDDEN",
            413: "FILE_TOO_LARGE",
            415: "UNSUPPORTED_MEDIA_TYPE",
            429: "RATE_LIMITED",
            503: "SERVICE_UNAVAILABLE",
        }
        return codes.get(self.status, "SERVER_ERROR")


class CygnEnterprise:
    """Cygn Enterprise API client."""

    def __init__(
        self,
        license_key: Optional[str] = None,
        cygn_token: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
        timeout: int = DEFAULT_TIMEOUT,
    ):
        if cygn_token:
            self._auth_header = f"Cygn {cygn_token}"
        elif license_key:
            self._auth_header = f"Bearer {license_key}"
        else:
            raise ValueError("Either cygn_token or license_key is required")
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout

    # ── Deepfake Detection ──────────────────────────────────────────────

    def detect_deepfake(
        self,
        content: str,
        media_type: str = "image",
    ) -> DetectDeepfakeResult:
        """Detect AI-generated content."""
        data = self._post("/detection/deepfake", {
            "content": content,
            "mediaType": media_type,
        })
        return DetectDeepfakeResult(
            success=data.get("success", False),
            media_type=data.get("mediaType", media_type),
            score=data.get("score", 0),
            verdict=data.get("verdict", ""),
            signals=[DeepfakeSignal(**s) for s in data.get("signals", [])],
            remaining=data.get("remaining"),
        )

    def detect_video_deepfake(self, content: str) -> DetectVideoDeepfakeResult:
        """Detect AI-generated video."""
        data = self._post("/detection/video-deepfake", {"content": content})
        return DetectVideoDeepfakeResult(
            success=data.get("success", False),
            score=data.get("score", 0),
            verdict=data.get("verdict", ""),
            frame_analysis=data.get("frameAnalysis", {}),
            audio_analysis=data.get("audioAnalysis", {}),
        )

    # ── C2PA Signing ────────────────────────────────────────────────────

    def sign_image(
        self,
        image: str,
        mime_type: str = "image/jpeg",
        provenance: str = "captured",
        title: Optional[str] = None,
        app_name: Optional[str] = None,
        did: Optional[str] = None,
        display_name: Optional[str] = None,
    ) -> SignImageResult:
        """Sign image with C2PA Content Credentials."""
        body: dict[str, Any] = {
            "image": image,
            "mimeType": mime_type,
            "provenance": provenance,
        }
        if title:
            body["title"] = title
        if app_name:
            body["appName"] = app_name
        if did:
            body["did"] = did
        if display_name:
            body["displayName"] = display_name

        data = self._post("/signing/c2pa", body)
        return SignImageResult(
            success=data.get("success", False),
            signed_image=data.get("signedImage", ""),
            mime_type=data.get("mimeType", mime_type),
            provenance=data.get("provenance", provenance),
            signed_at=data.get("signedAt", ""),
            enterprise=_parse_enterprise(data.get("enterprise")),
        )

    def sign_document(
        self,
        document: str,
        mime_type: str,
        title: Optional[str] = None,
        app_name: Optional[str] = None,
        did: Optional[str] = None,
        display_name: Optional[str] = None,
    ) -> SignDocumentResult:
        """Sign document with C2PA Content Credentials."""
        body: dict[str, Any] = {
            "document": document,
            "mimeType": mime_type,
        }
        if title:
            body["title"] = title
        if app_name:
            body["appName"] = app_name
        if did:
            body["did"] = did
        if display_name:
            body["displayName"] = display_name

        data = self._post("/signing/document", body)
        return SignDocumentResult(
            success=data.get("success", False),
            signed_document=data.get("signedDocument", ""),
            mime_type=data.get("mimeType", mime_type),
            signed_at=data.get("signedAt", ""),
            enterprise=_parse_enterprise(data.get("enterprise")),
        )

    # ── Provenance Bundle ───────────────────────────────────────────────

    def provenance_bundle(
        self,
        image: str,
        mime_type: str = "image/jpeg",
        provenance: str = "captured",
        title: Optional[str] = None,
        app_name: Optional[str] = None,
        did: Optional[str] = None,
        display_name: Optional[str] = None,
        skip_deepfake_check: bool = False,
    ) -> ProvenanceBundleResult:
        """Deepfake detection + C2PA signing in one call."""
        body: dict[str, Any] = {
            "image": image,
            "mimeType": mime_type,
            "provenance": provenance,
            "skipDeepfakeCheck": skip_deepfake_check,
        }
        if title:
            body["title"] = title
        if app_name:
            body["appName"] = app_name
        if did:
            body["did"] = did
        if display_name:
            body["displayName"] = display_name

        data = self._post("/provenance/bundle", body)
        df = data.get("deepfake", {})
        return ProvenanceBundleResult(
            success=data.get("success", False),
            signed_image=data.get("signedImage", ""),
            mime_type=data.get("mimeType", mime_type),
            deepfake=DeepfakeAnalysis(
                score=df.get("score", 0),
                verdict=df.get("verdict", ""),
                signals=[DeepfakeSignal(**s) for s in df.get("signals", [])],
            ),
            signed_at=data.get("signedAt", ""),
            enterprise=_parse_enterprise(data.get("enterprise")),
        )

    # ── Sonic Watermarking ──────────────────────────────────────────────

    def sonic_embed(
        self,
        audio: str,
        did: str,
        display_name: Optional[str] = None,
    ) -> SonicEmbedResult:
        """Embed invisible audio watermark."""
        body: dict[str, Any] = {"audio": audio, "did": did}
        if display_name:
            body["displayName"] = display_name
        data = self._post("/sonic/embed", body)
        return SonicEmbedResult(
            success=data.get("success", False),
            watermark_id=data.get("watermarkId", ""),
            audio_hash=data.get("audioHash", ""),
            payload_hash=data.get("payloadHash", ""),
            embedded_at=data.get("embeddedAt", ""),
            audio=data.get("audio", ""),
        )

    def sonic_detect(self, audio: str) -> SonicDetectResult:
        """Detect audio watermark."""
        data = self._post("/sonic/detect", {"audio": audio})
        return SonicDetectResult(
            success=data.get("success", False),
            detected=data.get("detected", False),
            watermark_id=data.get("watermarkId"),
            did=data.get("did"),
            confidence=data.get("confidence"),
        )

    def sonic_register(
        self,
        watermark_id: str,
        did: str,
        display_name: Optional[str] = None,
        audio_hash: Optional[str] = None,
    ) -> SonicRegisterResult:
        """Register watermarked audio."""
        body: dict[str, Any] = {"watermarkId": watermark_id, "did": did}
        if display_name:
            body["displayName"] = display_name
        if audio_hash:
            body["audioHash"] = audio_hash
        data = self._post("/sonic/register", body)
        return SonicRegisterResult(
            success=data.get("success", False),
            registration_id=data.get("registrationId", ""),
            registered_at=data.get("registeredAt", ""),
        )

    def sonic_lookup(self, watermark_id: str) -> SonicLookupResult:
        """Look up watermark signer."""
        data = self._post("/sonic/lookup", {"watermarkId": watermark_id})
        return SonicLookupResult(
            success=data.get("success", False),
            found=data.get("found", False),
            did=data.get("did"),
            display_name=data.get("displayName"),
            registered_at=data.get("registeredAt"),
        )

    # ── Voice ID ────────────────────────────────────────────────────────

    def voice_id_enroll(
        self,
        audio: str,
        did: str,
        display_name: Optional[str] = None,
    ) -> VoiceEnrollResult:
        """Enroll voice biometric profile."""
        body: dict[str, Any] = {"audio": audio, "did": did}
        if display_name:
            body["displayName"] = display_name
        data = self._post("/voice-id/enroll", body)
        return VoiceEnrollResult(
            success=data.get("success", False),
            enrollment_id=data.get("enrollmentId", ""),
            did=data.get("did", did),
            enrolled_at=data.get("enrolledAt", ""),
        )

    def voice_id_verify(self, audio: str, did: str) -> VoiceVerifyResult:
        """Verify speaker identity."""
        data = self._post("/voice-id/verify", {"audio": audio, "did": did})
        return VoiceVerifyResult(
            success=data.get("success", False),
            match=data.get("match", False),
            confidence=data.get("confidence", 0),
            did=data.get("did", did),
        )

    # ── Usage ───────────────────────────────────────────────────────────

    def get_usage(self) -> UsageSummary:
        """Get current usage summary and balance."""
        data = self._get("/metered/usage")
        return UsageSummary(
            balance=data.get("balance", 0),
            total_spent=data.get("totalSpent", 0),
            current_month=data.get("currentMonth", {}),
        )

    def get_balance(self) -> int:
        """Get current balance in paise."""
        return self.get_usage().balance

    # ── Internal ────────────────────────────────────────────────────────

    def _post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", path, body)

    def _get(self, path: str) -> dict[str, Any]:
        return self._request("GET", path)

    def _request(
        self,
        method: str,
        path: str,
        body: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        url = f"{self._base_url}{path}"
        data = json.dumps(body).encode("utf-8") if body else None

        req = Request(url, data=data, method=method)
        req.add_header("Authorization", self._auth_header)
        req.add_header("Content-Type", "application/json")
        req.add_header("User-Agent", "CygnSDK-Python/1.0")

        try:
            with urlopen(req, timeout=self._timeout) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except HTTPError as e:
            try:
                err_data = json.loads(e.read().decode("utf-8"))
            except Exception:
                err_data = {"error": str(e)}
            raise CygnAPIError(
                status=e.code,
                error=err_data.get("error", str(e)),
                balance=err_data.get("balance"),
                cost=err_data.get("cost"),
            ) from None


def _parse_enterprise(data: Optional[dict[str, Any]]) -> Optional[EnterpriseInfo]:
    if not data:
        return None
    return EnterpriseInfo(
        company_name=data.get("companyName", ""),
        label=data.get("label", ""),
        signed_at=data.get("signedAt"),
    )
