"""Centralized lossiness detection for API field validation."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Flag, auto
from typing import Mapping


class RequestFeature(Flag):
    """Request feature bitset for capability detection."""

    NONE = 0
    TOOLS = auto()
    TOOL_CHOICE = auto()
    PARALLEL_TOOL_CALLS = auto()
    RESPONSE_FORMAT_JSON_SCHEMA = auto()
    RESPONSE_FORMAT_JSON_OBJECT = auto()
    STRUCTURED_OUTPUT = auto()
    STREAM = auto()
    STREAM_OPTIONS = auto()
    REASONING = auto()
    N_CHOICES = auto()
    LOGPROBS = auto()
    PRESENCE_PENALTY = auto()
    FREQUENCY_PENALTY = auto()
    LOGIT_BIAS = auto()
    USER = auto()
    METADATA = auto()


@dataclass
class LossinessReport:
    """Result of lossiness analysis for a request payload."""

    is_lossy: bool
    unsupported_fields: list[str] = field(default_factory=list)
    unsupported_features: list[str] = field(default_factory=list)
    lossiness_labels: list[str] = field(default_factory=list)
    recommendation: str = "pass"  # "400", "409", or "pass"

    def to_dict(self) -> dict[str, bool | list[str] | str]:
        """Return a serializable dict for error context."""
        return {
            "is_lossy": self.is_lossy,
            "unsupported_fields": self.unsupported_fields,
            "unsupported_features": self.unsupported_features,
            "lossiness": self.lossiness_labels,
            "recommendation": self.recommendation,
        }


CHAT_COMPLETIONS_FIELDS = frozenset(
    {
        "model",
        "messages",
        "stream",
        "temperature",
        "top_p",
        "n",
        "stop",
        "max_tokens",
        "max_completion_tokens",
        "seed",
        "tools",
        "tool_choice",
        "parallel_tool_calls",
        "response_format",
        "stream_options",
        "presence_penalty",
        "frequency_penalty",
        "logit_bias",
        "logprobs",
        "top_logprobs",
        "user",
        "metadata",
        "session_id",
        "idempotency_key",
        "policy",
        "reasoning",
        "reasoning_effort",
    }
)

MESSAGES_FIELDS = frozenset(
    {
        "model",
        "messages",
        "system",
        "max_tokens",
        "stream",
        "temperature",
        "top_p",
        "top_k",
        "stop_sequences",
        "tools",
        "tool_choice",
        "metadata",
        "session_id",
        "idempotency_key",
        "policy",
    }
)

RESPONSES_FIELDS = frozenset(
    {
        "model",
        "input",
        "instructions",
        "messages",
        "metadata",
        "response_format",
        "text",
        "tools",
        "tool_choice",
        "parallel_tool_calls",
        "stream",
        "max_output_tokens",
        "temperature",
        "top_p",
        "stop",
        "reasoning",
        "previous_response_id",
        "session_id",
        "idempotency_key",
        "policy",
        "user",
        "store",
        "include",
        "truncation",
    }
)


class LossinessDetector:
    """Validates request payloads for unsupported fields and features."""

    def check_fields(self, payload: Mapping[str, object], endpoint: str) -> LossinessReport:
        """Return a LossinessReport for unsupported top-level fields."""
        if endpoint == "chat":
            supported = CHAT_COMPLETIONS_FIELDS
        elif endpoint == "messages":
            supported = MESSAGES_FIELDS
        else:
            supported = RESPONSES_FIELDS
        unsupported = sorted(set(payload.keys()) - supported)

        if unsupported:
            return LossinessReport(
                is_lossy=True,
                unsupported_fields=unsupported,
                lossiness_labels=[f"unsupported_field_{f}" for f in unsupported],
                recommendation="409",
            )

        return LossinessReport(is_lossy=False, recommendation="pass")

    def check_features(self, payload: Mapping[str, object], endpoint: str) -> LossinessReport:
        """Return a LossinessReport for unsupported feature combinations."""
        unsupported_features: list[str] = []
        lossiness_labels: list[str] = []

        # Check for structured output (json_schema) which is not yet supported
        if endpoint == "responses":
            text_field = payload.get("text")
            text_format = text_field.get("format") if isinstance(text_field, dict) else None
            rf = text_format if text_format is not None else payload.get("response_format")
        else:
            rf = payload.get("response_format")

        if isinstance(rf, dict):
            rft = str(rf.get("type") or "").lower()
            if rft == "json_schema":
                unsupported_features.append("response_format.json_schema")
                lossiness_labels.append("structured_output_not_supported")
            elif rft == "json_object":
                unsupported_features.append("response_format.json_object")
                lossiness_labels.append("json_mode_not_supported")

        if unsupported_features:
            return LossinessReport(
                is_lossy=True,
                unsupported_features=unsupported_features,
                lossiness_labels=lossiness_labels,
                recommendation="409",
            )

        return LossinessReport(is_lossy=False, recommendation="pass")

    def check_request(self, payload: Mapping[str, object], endpoint: str) -> LossinessReport:
        """Combine field and feature validation into a single report."""
        # First check fields
        field_report = self.check_fields(payload, endpoint)
        if field_report.is_lossy:
            return field_report

        # Then check features
        feature_report = self.check_features(payload, endpoint)
        return feature_report


# Module-level singleton for convenience
_detector: LossinessDetector | None = None


def get_lossiness_detector() -> LossinessDetector:
    """Return the module-level LossinessDetector singleton."""
    global _detector
    if _detector is None:
        _detector = LossinessDetector()
    return _detector


__all__ = [
    "LossinessDetector",
    "LossinessReport",
    "RequestFeature",
    "CHAT_COMPLETIONS_FIELDS",
    "MESSAGES_FIELDS",
    "RESPONSES_FIELDS",
    "get_lossiness_detector",
]
