"""Dataclasses that mirror the ChatAds Go API request/response models."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

FUNCTION_ITEM_OPTIONAL_FIELDS = (
    "ip",
    "country",
    "input_type",
    "extraction_mode",
    "resolution_mode",
    "image_title_filter",
)

_FIELD_TO_PAYLOAD_KEY = {
    "ip": "ip",
    "country": "country",
    "input_type": "input_type",
    "extraction_mode": "extraction_mode",
    "resolution_mode": "resolution_mode",
    "image_title_filter": "image_title_filter",
}

RESERVED_PAYLOAD_KEYS = frozenset({"message", *(_FIELD_TO_PAYLOAD_KEY.values())})


@dataclass
class Product:
    """Product metadata from resolution."""
    title: Optional[str] = None
    description: Optional[str] = None
    stars: Optional[float] = None
    reviews: Optional[int] = None
    image: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> Optional["Product"]:
        if not data:
            return None
        return cls(
            title=data.get("title"),
            description=data.get("description"),
            stars=data.get("stars"),
            reviews=data.get("reviews"),
            image=data.get("image"),
        )


@dataclass
class Offer:
    """Single affiliate offer returned by the API.

    URL is populated for resolved offers but may be empty in extraction-only mode (resolution_mode=none).
    """
    link_text: str
    url: str  # Empty when resolution_mode=none (extraction-only)
    search_term: Optional[str] = None  # Verbose mode only
    confidence_level: Optional[str] = None  # Verbose mode only
    confidence_score: Optional[float] = None  # Verbose mode only
    resolution_source: Optional[str] = None  # Verbose mode only
    product: Optional[Product] = None
    offer_source: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> Optional["Offer"]:
        if not data:
            return None
        return cls(
            link_text=data.get("link_text", ""),
            url=data.get("url", ""),
            search_term=data.get("search_term"),
            confidence_level=data.get("confidence_level"),
            confidence_score=data.get("confidence_score"),
            resolution_source=data.get("resolution_source"),
            product=Product.from_dict(data.get("product")),
            offer_source=data.get("offer_source"),
        )


@dataclass
class AnalyzeData:
    """Response data containing affiliate offers.

    status is the single source of truth for the outcome:
    - "filled": All requested offers filled (returned == requested)
    - "partial_fill": Some offers filled (0 < returned < requested)
    - "no_offer": No affiliate offer available (non-billable when reason is no_brand_found or not_monetizable)
    - "message_too_short": Message below minimum word count (non-billable)
    - "message_too_long": Message above maximum character count (non-billable)
    - "country_not_allowed": Country not in allowed list (non-billable)
    - "blocked_keyword": Message contains blocked content (non-billable)
    - "language_not_allowed": Language not in allowed list (non-billable)
    - "ip_country_not_allowed": IP country not in allowed list (non-billable)
    """
    status: str  # "filled" | "partial_fill" | "no_offer" | "message_too_short" | "message_too_long" | "country_not_allowed" | "blocked_keyword" | "language_not_allowed" | "ip_country_not_allowed"
    offers: List[Offer]  # Returned offers; URL may be empty for extraction-only mode, never None
    requested: int
    returned: int  # Count of filled offers (len(offers))
    billed: bool = False
    extraction_source: Optional[str] = None  # Verbose mode only
    extraction_debug: Optional[List[Any]] = None  # Verbose mode only
    resolution_debug: Optional[List[Any]] = None  # Verbose mode only
    input_type: Optional[str] = None  # Verbose mode only

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> "AnalyzeData":
        if not data:
            return cls(status="no_offer", offers=[], requested=0, returned=0)
        offers_data = data.get("offers") or []
        offers = [Offer.from_dict(o) for o in offers_data if o]
        return cls(
            status=data.get("status", "no_offer"),
            offers=[o for o in offers if o is not None],
            requested=int(data.get("requested", 0)),
            returned=int(data.get("returned", 0)),
            billed=bool(data.get("billed", False)),
            extraction_source=data.get("extraction_source"),
            extraction_debug=data.get("extraction_debug"),
            resolution_debug=data.get("resolution_debug"),
            input_type=data.get("input_type"),
        )


@dataclass
class ChatAdsError:
    code: str
    message: str
    details: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> Optional["ChatAdsError"]:
        if not data:
            return None
        return cls(
            code=data.get("code", "UNKNOWN"),
            message=data.get("message", ""),
            details=data.get("details") or {},
        )


@dataclass
class UsageInfo:
    """Usage information returned in API responses."""
    monthly_requests: int
    is_free_tier: bool
    free_tier_limit: Optional[int] = None
    free_tier_remaining: Optional[int] = None
    daily_requests: Optional[int] = None
    daily_limit: Optional[int] = None

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> Optional["UsageInfo"]:
        if not data:
            return None
        return cls(
            monthly_requests=int(data.get("monthly_requests") or 0),
            is_free_tier=bool(data.get("is_free_tier", False)),
            free_tier_limit=_maybe_int(data.get("free_tier_limit")),
            free_tier_remaining=_maybe_int(data.get("free_tier_remaining")),
            daily_requests=_maybe_int(data.get("daily_requests")),
            daily_limit=_maybe_int(data.get("daily_limit")),
        )


@dataclass
class ChatAdsMeta:
    """Metadata about the API request and response."""
    request_id: str
    timestamp: Optional[str] = None
    version: Optional[str] = None
    country: Optional[str] = None
    usage: Optional[UsageInfo] = None
    timing_ms: Optional[Dict[str, float]] = None
    raw: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_dict(cls, data: Optional[Dict[str, Any]]) -> "ChatAdsMeta":
        data = data or {}
        return cls(
            request_id=data.get("request_id", ""),
            timestamp=data.get("timestamp"),
            version=data.get("version"),
            country=data.get("country"),
            usage=UsageInfo.from_dict(data.get("usage")),
            timing_ms=data.get("timing_ms"),
            raw=data,
        )


@dataclass
class ChatAdsResponse:
    meta: ChatAdsMeta
    data: AnalyzeData = field(default_factory=lambda: AnalyzeData(status="no_offer", offers=[], requested=0, returned=0))
    error: Optional[ChatAdsError] = None
    raw: Dict[str, Any] = field(default_factory=dict)

    @property
    def success(self) -> bool:
        """Returns True if error is None (successful response)."""
        return self.error is None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ChatAdsResponse":
        data = data or {}
        return cls(
            data=AnalyzeData.from_dict(data.get("data")),
            error=ChatAdsError.from_dict(data.get("error")),
            meta=ChatAdsMeta.from_dict(data.get("meta")),
            raw=data,
        )


@dataclass
class FunctionItemPayload:
    """Subset of the server's request model.

    Contains the allowed fields per the OpenAPI spec.
    """

    message: str
    ip: Optional[str] = None
    country: Optional[str] = None
    input_type: Optional[str] = None
    extraction_mode: Optional[str] = None
    resolution_mode: Optional[str] = None
    image_title_filter: Optional[str] = None
    extra_fields: Dict[str, Any] = field(default_factory=dict)

    def to_payload(self) -> Dict[str, Any]:
        payload = {"message": self.message}
        for field_name, payload_key in _FIELD_TO_PAYLOAD_KEY.items():
            value = getattr(self, field_name)
            if value is not None:
                payload[payload_key] = value

        conflicts = RESERVED_PAYLOAD_KEYS.intersection(self.extra_fields.keys())
        if conflicts:
            conflict_list = ", ".join(sorted(conflicts))
            raise ValueError(
                f"extra_fields contains reserved keys that would override core payload data: {conflict_list}"
            )
        payload.update(self.extra_fields)
        return payload


def _maybe_int(value: Any) -> Optional[int]:
    try:
        return int(value)
    except (TypeError, ValueError):
        return None
