# --- Pydantic Models for GPC 3.0 ---
from typing import List, Optional, Union, Literal, Any, Dict, ForwardRef, Annotated

from pydantic import BaseModel, Field, validator, root_validator

# --- Forward References for Recursive Predicates ---
# Define ForwardRefs for types used before their full definition
NaryQueryPredicateRef = ForwardRef("NaryQueryPredicate")
UnaryQueryPredicateRef = ForwardRef("UnaryQueryPredicate")
AttributeQueryPredicateRef = ForwardRef("AttributeQueryPredicate")
SelectionQueryPredicateRef = ForwardRef("SelectionQueryPredicate")

# --- Base Models & Enums ---


class BaseGPCModel(BaseModel):
    """Base class for all GPC models."""
    class Config:
        allow_population_by_field_name = True
        arbitrary_types_allowed = True
        extra = "allow"  # Allow extra fields not in the model


class User(BaseGPCModel):
    """User information."""
    id: Optional[str] = None
    access_token: Optional[str] = None


class Location(BaseGPCModel):
    """Location information."""
    originating_locale: Optional[str] = Field(None, alias="originatingLocale")
    country_code: Optional[str] = Field(None, alias="countryCode")


class Advertising(BaseGPCModel):
    """Advertising information."""
    limit_ad_tracking: Optional[bool] = Field(None, alias="limitAdTracking")
    advertising_id: Optional[str] = Field(None, alias="advertisingId")


class RequestContext(BaseGPCModel):
    """Request context."""
    user: Optional[User] = None
    location: Optional[Location] = None
    advertising: Optional[Advertising] = None


class ResolvedEntity(BaseGPCModel):
    """Represents a single potential resolution for an entity mentioned."""

    entity_id: str = Field(
        ...,
        description="Skill-provided canonical identifier for this specific entity (e.g., artist ID, track ID).",
    )
    entity_name: Optional[str] = Field(
        None,
        description="Optional: Human-readable name (e.g., 'Taylor Swift'). Useful for logging/debugging.",
    )
    confidence: Optional[float] = Field(
        None,
        description="Optional: Alexa's confidence in this specific resolution (0.0-1.0).",
    )


class ResolvedSelectionCriteriaAttribute(BaseGPCModel):
    """
    Represents a single piece of information (attribute) extracted from the user's utterance.
    Can represent catalog entities, media types, sort orders etc.
    """

    id: str = Field(
        ...,
        description="Unique identifier for this attribute instance within the current request. Used for referencing in predicates.",
    )
    type: str = Field(
        ...,
        description="The type of attribute (e.g., 'ARTIST', 'ALBUM', 'GENRE', 'MEDIA_TYPE', 'SORT', 'PLAYLIST', 'TRACK', 'STATION', 'PROGRAM_SERIES', 'PROGRAM', 'RADIO', 'BOOK', 'AUTHOR').",
    )
    raw_value: Optional[str] = Field(
        None,
        description="The raw text spoken by the user that mapped to this attribute (e.g., 'Taylor'). Replaces deprecated RawSelectionCriteria.",
    )
    value: Optional[str] = Field(
        None,
        description="For non-catalog attributes like MEDIA_TYPE ('SONGS', 'ALBUMS') or SORT ('POPULARITY', 'RECENT').",
    )
    resolved_entities: Optional[List[ResolvedEntity]] = Field(
        None,
        description="For catalog attributes (ARTIST, ALBUM etc.): A ranked list of possible entity resolutions. The first item is the most likely.",
    )

    @validator("type")
    def check_type_known(cls, v):
        # Add known types for better validation if desired, but keeping it flexible
        # known_types = {'ARTIST', 'ALBUM', ...}
        # if v not in known_types: print(f"Warning: Unknown attribute type: {v}")
        return v


# --- Predicate Models (for building the Query Tree) ---


class BaseQueryPredicate(BaseGPCModel):
    """Base class for all predicate types."""

    type: Literal["NARY", "UNARY", "ATTRIBUTE"] = Field(
        ..., description="Discriminator field for the predicate type."
    )


class NaryQueryPredicate(BaseQueryPredicate):
    """Combines multiple sub-predicates with UNION or INTERSECTION logic."""

    type: Literal["NARY"] = Field(
        "NARY", description="Indicates this is an N-ary predicate."
    )
    condition: Literal["UNION", "INTERSECTION"] = Field(
        ...,
        description="How to combine the sub-predicates. UNION (OR logic), INTERSECTION (AND logic).",
    )
    predicates: List["SelectionQueryPredicate"] = Field(
        ..., description="List of sub-predicates to combine."
    )


class UnaryQueryPredicate(BaseQueryPredicate):
    """Applies a condition (modifier) like SIMILAR or NOT to a single sub-predicate."""

    type: Literal["UNARY"] = Field(
        "UNARY", description="Indicates this is a Unary predicate."
    )
    condition: Literal["SIMILAR", "NOT"] = Field(
        ...,
        description="Condition to apply. SIMILAR (find related items), NOT (exclude items).",
    )
    predicate: "SelectionQueryPredicate" = Field(
        ..., description="The sub-predicate being modified."
    )


class AttributeQueryPredicate(BaseQueryPredicate):
    """A leaf node in the query tree, referencing a specific attribute by its ID."""

    type: Literal["ATTRIBUTE"] = Field(
        "ATTRIBUTE", description="Indicates this is an Attribute predicate (leaf node)."
    )
    attribute_id: str = Field(
        ...,
        alias="attributeId",
        description="The 'id' of the ResolvedSelectionCriteriaAttribute to use.",
    )


# --- Union type for predicates ---
SelectionQueryPredicate = Union[
    NaryQueryPredicate, UnaryQueryPredicate, AttributeQueryPredicate
]

# Update forward references now that all models are defined
# NaryQueryPredicate.model_rebuild()
# UnaryQueryPredicate.model_rebuild()
# AttributeQueryPredicate needs no forward refs
# SelectionQueryPredicate doesn't need model_rebuild itself, its members do


# --- Action Model ---
class GPCAction(BaseGPCModel):
    """Represents an explicit action requested within the payload."""

    type: Literal["GENERATE_CONTENT"]  # Add other potential action types if needed


# --- Polymorphic Selection Criteria Models ---


class BaseResolvedSelectionCriteria(BaseGPCModel):
    """Base for the different ways Alexa interprets the user query."""

    id: str = Field(
        ...,
        description="Unique identifier for this specific interpretation of the user's query.",
    )
    type: Literal["ATTRIBUTES", "NL_QUERY"] = Field(
        ..., description="Discriminator field for the criteria type."
    )


class Completeness(BaseGPCModel):
    """Indicates how well a MultiAttributeSelectionCriteria captures user intent."""

    bin: Literal["HIGH", "MEDIUM", "LOW"] = Field(
        ..., description="Categorical assessment of completeness."
    )
    score: Optional[float] = Field(
        None,
        description="Numerical score (0.0-1.0) indicating completeness. Higher is better.",
    )


class MultiAttributeSelectionCriteria(BaseResolvedSelectionCriteria):
    """Structured interpretation using attributes and optional query predicates."""

    type: Literal["ATTRIBUTES"] = Field(
        "ATTRIBUTES", description="Indicates structured attribute-based criteria."
    )
    attributes: List[ResolvedSelectionCriteriaAttribute] = Field(
        ...,
        description="List of attributes extracted. Relationships defined by the 'query' field (or default INTERSECTION).",
    )
    query: Optional[SelectionQueryPredicate] = Field(
        None,
        description="Optional predicate tree defining relationships (INTERSECTION, UNION, SIMILAR, NOT) between attributes. If None, default behavior is INTERSECTION of all attributes.",
    )
    completeness: Optional[Completeness] = Field(
        None,
        description="How well this structured interpretation captures the full user intent.",
    )
    request_context: Optional[RequestContext] = Field(
        None,
        description="The request context associated with this selection criteria.",
    )


class NaturalLanguageSelectionCriteria(BaseResolvedSelectionCriteria):
    """Free-form text interpretation of the user query."""

    type: Literal["NL_QUERY"] = Field(
        "NL_QUERY", description="Indicates natural language query criteria."
    )
    query: str = Field(
        ...,
        description="The natural language query string generated by Alexa (not the raw user utterance). Use this with your own NLU/search if supported.",
    )


# --- Union type for the ranked list items ---
# Use Annotated and Field discriminator for robust type differentiation
ResolvedSelectionCriteria = Annotated[
    Union[MultiAttributeSelectionCriteria, NaturalLanguageSelectionCriteria],
    Field(discriminator="type"),
]


# --- Request Header Model ---
class GPCRequestHeader(BaseGPCModel):
    """Structure of the 'header' object."""

    message_id: str = Field(..., alias="messageId")
    namespace: Literal["Alexa.Media.Search"]
    name: Literal["GetPlayableContent", "GetDisplayableContent"]
    payload_version: Literal["3.0"] = Field(..., alias="payloadVersion")


# --- Filters Model ---
class Filters(BaseGPCModel):
    """Placeholder for filters - add relevant fields."""

    explicit_content_filter: Optional[bool] = Field(
        None, alias="explicitLanguageAllowed"
    )  # Corrected alias and type based on example
    # TODO: Add other filters like 'territory', 'musicStreamingProvider' etc. if used


# --- Main Request Payload Model ---
class GPCRequestPayload(BaseGPCModel):
    """Structure of the 'payload' object in GetPlayableContent/GetDisplayableContent v3.0 requests."""

    ranked_selection_criteria: List[ResolvedSelectionCriteria] = Field(
        ...,
        alias="rankedSelectionCriteria",
        description="Ranked list of interpretations of the user's query. Process these in order.",
    )
    request_context: Optional[RequestContext] = Field(None, alias="requestContext")
    filters: Optional[Filters] = Field(None)
    action: Optional[GPCAction] = Field(
        None, description="Optional action requested, e.g., generate content."
    )  # Added action field
    # Fields specific to GetDisplayableContent
    max_result_limit: Optional[int] = Field(None, alias="maxResultLimit")
    play_queue_preview_criteria: Optional[Any] = Field(
        None, alias="playQueuePreviewCriteria"
    )  # Define if needed
    endpoints: Optional[List[Any]] = Field(None)  # Define if needed
    # Other observed fields (optional, add if needed for logic)
    policies: Optional[Any] = Field(None)
    response_options: Optional[Any] = Field(None, alias="responseOptions")
    raw_text: Optional[str] = Field(None, alias="rawText")
    experience: Optional[Any] = Field(None)


# --- Full GPC Request Model ---
class GPCRequest(BaseGPCModel):
    """The complete incoming request object for GPC v3.0."""

    header: GPCRequestHeader
    payload: GPCRequestPayload


# --- Pydantic Models for GPC 3.0 Response Payload ---


class MatchedCriteria(BaseGPCModel):
    """Included in the response to indicate which criteria interpretation was used."""

    criteria_id: str = Field(
        ...,
        alias="criteriaId",
        description="The 'id' of the ResolvedSelectionCriteria object from the request that you used to generate the response content.",
    )


class GPCResponsePayload(BaseGPCModel):
    """Base structure for V3.0 response payloads."""

    content: Optional[Any]  # For GetPlayableContent - Define your content structure
    content_groups: Optional[
        Any
    ]  # For GetDisplayableContent - Define your content structure
    matched_criteria: Optional[MatchedCriteria] = Field(
        None,
        alias="matchedCriteria",
        description="Reference to the request criteria used.",
    )

    # Removed Config from here as it's inherited from BaseGPCModel


# Example specific response (adapt content structure as needed)
class GetPlayableContentResponsePayload(GPCResponsePayload):
    content: List[Dict[str, Any]]  # Replace Any with your actual Content object model


class GetDisplayableContentResponsePayload(GPCResponsePayload):
    content_groups: List[
        Dict[str, Any]
    ]  # Replace Any with your actual ContentGroup object model
