"""
TypedDict definitions for pipeline stage inputs and outputs.

These contracts define the data structures passed between pipeline stages,
enabling independent development and validation of each stage.
"""

from __future__ import annotations

from datetime import date
from typing import Any, Literal, TypedDict


# =============================================================================
# Common Types
# =============================================================================

ValenceType = Literal["V+", "V-", "V0", "V±"]
IntensityType = Literal["I1", "I2", "I3"]
SpecificityType = Literal["S1", "S2", "S3"]
ActionabilityType = Literal["A1", "A2", "A3"]
TemporalType = Literal["TC", "TR", "TH", "TF"]
EvidenceType = Literal["ES", "EI", "EC"]
ComparativeType = Literal["CR-N", "CR-B", "CR-W", "CR-S"]
ConfidenceType = Literal["high", "medium", "low"]
EntityTypeValue = Literal["location", "staff", "product", "process", "time", "other"]
RelationType = Literal["cause_of", "effect_of", "contrast", "resolution"]
ProfileType = Literal["lite", "core", "standard", "full"]
BucketType = Literal["day", "week", "month"]
SubjectType = Literal["overall", "urt_code", "domain", "issue"]
IssueState = Literal["open", "resolved", "ignored", "merged"]


# =============================================================================
# Validation Types
# =============================================================================


class ValidationError(TypedDict):
    """A single validation error."""

    rule: str
    identifier: str
    message: str


class ValidationResult(TypedDict):
    """Result of validating a stage output."""

    stage: str
    passed: bool
    error_count: int
    errors: list[ValidationError]


# =============================================================================
# Stage 0: Raw Ingestion (from Scraper)
# =============================================================================


class BusinessInfo(TypedDict):
    """Business metadata from scraper."""

    name: str
    address: str
    category: str
    total_reviews: int
    average_rating: float


class RawReview(TypedDict, total=False):
    """Raw review as scraped from Google Maps."""

    review_id: str
    author_name: str
    author_id: str | None
    rating: int
    text: str | None
    review_time: str
    response_text: str | None
    response_time: str | None
    photos: list[str] | None
    raw_payload: dict[str, Any]


class ScraperOutput(TypedDict):
    """Output from the scraper (Stage 0), input to pipeline."""

    job_id: str
    status: Literal["completed", "failed", "partial"]
    business_id: str
    place_id: str
    business_info: BusinessInfo
    reviews: list[RawReview]
    scrape_time_ms: int
    reviews_scraped: int
    scraper_version: str


# =============================================================================
# Stage 1: Normalization
# =============================================================================


class Stage1Input(TypedDict):
    """Input to Stage 1 normalization."""

    job_id: str
    business_id: str
    place_id: str
    reviews: list[RawReview]


class NormalizedReview(TypedDict, total=False):
    """A normalized review ready for classification."""

    # Identity (composite key)
    source: Literal["google"]
    review_id: str
    review_version: int

    # Tenant context
    business_id: str
    place_id: str

    # Content
    text: str
    text_normalized: str
    text_language: str
    text_length: int
    word_count: int

    # Metadata
    rating: int
    review_time: str
    author_name: str
    author_id: str | None

    # Dedup
    content_hash: str
    dedup_group_id: str | None

    # Reference
    raw_id: int


class Stage1Stats(TypedDict):
    """Statistics from Stage 1 processing."""

    input_count: int
    output_count: int
    skipped_empty: int
    skipped_duplicate: int


class Stage1Output(TypedDict):
    """Output from Stage 1 normalization."""

    job_id: str
    business_id: str
    place_id: str
    reviews_normalized: list[NormalizedReview]
    stats: Stage1Stats


# =============================================================================
# Stage 2: LLM Classification
# =============================================================================


class ReviewToClassify(TypedDict):
    """A review to be classified by the LLM."""

    source: str
    review_id: str
    review_version: int
    business_id: str
    place_id: str
    text: str
    text_normalized: str
    rating: int
    review_time: str


class ClassificationConfig(TypedDict, total=False):
    """Configuration for LLM classification."""

    model: str
    taxonomy_version: str
    profile: ProfileType
    max_spans_per_review: int
    job_id: str | None  # Optional job_id for tracking


class Stage2Input(TypedDict):
    """Input to Stage 2 classification."""

    reviews: list[ReviewToClassify]
    config: ClassificationConfig


class CausalLink(TypedDict):
    """A link in a causal chain."""

    code: str
    role: Literal["cause", "effect", "context", "outcome"]
    order: int


class ExtractedSpan(TypedDict, total=False):
    """A span extracted from a review with URT classification."""

    # Identity
    span_id: str
    span_index: int

    # Position (offsets into original text)
    span_text: str
    span_start: int
    span_end: int

    # Classification
    profile: ProfileType
    urt_primary: str
    urt_secondary: list[str]
    valence: ValenceType
    intensity: IntensityType
    comparative: ComparativeType

    # Extended (standard/full profile)
    specificity: SpecificityType
    actionability: ActionabilityType
    temporal: TemporalType
    evidence: EvidenceType

    # Entity
    entity: str | None
    entity_type: EntityTypeValue | None
    entity_normalized: str | None

    # Causal (full profile)
    relation_type: RelationType | None
    related_span_index: int | None
    causal_chain: list[CausalLink] | None

    # Metadata
    confidence: ConfidenceType
    usn: str

    # Flags
    is_primary: bool


class ClassifiedReview(TypedDict, total=False):
    """A review with LLM classification results."""

    # Identity
    source: str
    review_id: str
    review_version: int

    # Review-level classification (from primary span)
    urt_primary: str
    urt_secondary: list[str]
    valence: ValenceType
    intensity: IntensityType
    comparative: ComparativeType

    # Extracted entities
    staff_mentions: list[str]
    quotes: dict[str, str]

    # Trust score
    trust_score: float

    # Embedding
    embedding: list[float]

    # Spans
    spans: list[ExtractedSpan]

    # Processing metadata
    classification_confidence: dict[str, float]
    processing_time_ms: int


class Stage2Stats(TypedDict):
    """Statistics from Stage 2 processing."""

    input_count: int
    success_count: int
    error_count: int
    total_spans: int
    avg_spans_per_review: float
    llm_tokens_used: int
    llm_cost_usd: float


class Stage2Output(TypedDict):
    """Output from Stage 2 classification."""

    batch_id: str
    taxonomy_version: str
    model_version: str
    prompt_version: str
    reviews_classified: list[ClassifiedReview]
    stats: Stage2Stats


# =============================================================================
# Stage 3: Issue Routing
# =============================================================================


class SpanToRoute(TypedDict):
    """A span to be routed to an issue."""

    span_id: str
    business_id: str
    place_id: str
    urt_primary: str
    valence: str
    intensity: str
    entity_normalized: str | None
    review_time: str
    confidence: str
    trust_score: float


class Stage3Input(TypedDict):
    """Input to Stage 3 issue routing."""

    spans: list[SpanToRoute]
    job_id: str | None  # Optional job_id for linking issues to pipeline executions


class RoutedSpan(TypedDict):
    """A span that has been routed to an issue."""

    span_id: str
    issue_id: str
    routing_key: str
    is_new_issue: bool


class Stage3Stats(TypedDict):
    """Statistics from Stage 3 processing."""

    spans_processed: int
    spans_routed: int
    spans_skipped: int
    issues_created: int
    issues_updated: int


class Stage3Output(TypedDict):
    """Output from Stage 3 issue routing."""

    routed_spans: list[RoutedSpan]
    issues_created: list[str]
    issues_updated: list[str]
    stats: Stage3Stats


# =============================================================================
# Stage 4: Fact Aggregation
# =============================================================================


class Stage4Input(TypedDict):
    """Input to Stage 4 fact aggregation."""

    business_id: str
    date: str
    bucket_types: list[BucketType]
    taxonomy_version: str


class FactRecord(TypedDict, total=False):
    """An aggregated fact record for time series data."""

    # Keys
    business_id: str
    place_id: str
    period_date: date
    bucket_type: str
    subject_type: SubjectType
    subject_id: str
    taxonomy_version: str

    # Metrics
    review_count: int
    span_count: int
    negative_count: int
    positive_count: int
    neutral_count: int
    mixed_count: int
    strength_score: float
    negative_strength: float
    positive_strength: float
    avg_rating: float | None
    i1_count: int
    i2_count: int
    i3_count: int
    cr_better: int
    cr_worse: int
    cr_same: int
    trust_weighted_strength: float
    trust_weighted_negative: float


class Stage4Stats(TypedDict):
    """Statistics from Stage 4 processing."""

    business_id: str
    date: str
    locations_processed: int
    codes_aggregated: int
    facts_upserted: int


class Stage4Output(TypedDict):
    """Output from Stage 4 fact aggregation."""

    facts_written: list[FactRecord]
    stats: Stage4Stats


# =============================================================================
# Database Entity Types
# =============================================================================


class ReviewRaw(TypedDict, total=False):
    """A raw review record in the database."""

    id: int
    source: str
    review_id: str
    place_id: str
    raw_payload: dict[str, Any]
    review_text: str | None
    rating: int
    review_time: str
    reviewer_name: str
    reviewer_id: str | None
    review_version: int
    pulled_at: str
    created_at: str


class ReviewEnriched(TypedDict, total=False):
    """An enriched review record in the database."""

    id: int
    source: str
    review_id: str
    review_version: int
    is_latest: bool
    raw_id: int
    business_id: str
    place_id: str
    text: str
    text_normalized: str
    rating: int
    review_time: str
    language: str
    taxonomy_version: str
    urt_primary: str | None
    urt_secondary: list[str] | None
    valence: ValenceType | None
    intensity: IntensityType | None
    comparative: ComparativeType | None
    staff_mentions: list[str] | None
    quotes: dict[str, str] | None
    embedding: list[float] | None
    trust_score: float | None
    classification_model: str | None
    classification_confidence: dict[str, float] | None
    processed_at: str | None
    created_at: str


class ReviewSpan(TypedDict, total=False):
    """A span record in the database."""

    id: int
    span_id: str
    business_id: str
    place_id: str
    source: str
    review_id: str
    review_version: int
    span_index: int
    span_text: str
    span_start: int
    span_end: int
    profile: ProfileType
    urt_primary: str
    urt_secondary: list[str]
    valence: ValenceType
    intensity: IntensityType
    comparative: ComparativeType
    specificity: SpecificityType | None
    actionability: ActionabilityType | None
    temporal: TemporalType | None
    evidence: EvidenceType | None
    entity: str | None
    entity_type: EntityTypeValue | None
    entity_normalized: str | None
    relation_type: RelationType | None
    related_span_id: str | None
    causal_chain: list[CausalLink] | None
    is_primary: bool
    is_active: bool
    review_time: str
    confidence: ConfidenceType
    usn: str
    taxonomy_version: str
    model_version: str
    ingest_batch_id: str
    created_at: str


class Issue(TypedDict, total=False):
    """An issue record in the database."""

    id: int
    issue_id: str
    business_id: str
    place_id: str
    primary_subcode: str
    domain: str
    state: IssueState
    priority_score: float
    confidence_score: float
    span_count: int
    max_intensity: IntensityType
    entity: str | None
    entity_normalized: str | None
    taxonomy_version: str
    created_at: str
    updated_at: str


class IssueSpan(TypedDict):
    """A link between an issue and a span."""

    id: int
    issue_id: str
    span_id: str
    source: str
    review_id: str
    review_version: int
    is_primary_match: bool
    intensity: IntensityType
    review_time: str
    created_at: str


class IssueEvent(TypedDict, total=False):
    """An event in the issue audit log."""

    id: int
    issue_id: str
    event_type: str
    span_id: str | None
    old_value: str | None
    new_value: str | None
    metadata: dict[str, Any] | None
    created_at: str


class FactTimeseries(TypedDict, total=False):
    """A fact time series record in the database."""

    id: int
    business_id: str
    place_id: str
    period_date: date
    bucket_type: BucketType
    subject_type: SubjectType
    subject_id: str
    taxonomy_version: str
    review_count: int
    span_count: int
    negative_count: int
    positive_count: int
    neutral_count: int
    mixed_count: int
    strength_score: float
    negative_strength: float
    positive_strength: float
    avg_rating: float | None
    i1_count: int
    i2_count: int
    i3_count: int
    cr_better: int
    cr_worse: int
    cr_same: int
    trust_weighted_strength: float
    trust_weighted_negative: float
    computed_at: str
    created_at: str


# =============================================================================
# LLM Response Types
# =============================================================================


class LLMSpanResponse(TypedDict, total=False):
    """A span in the LLM response."""

    span_index: int
    span_text: str
    span_start: int
    span_end: int
    urt_primary: str
    urt_secondary: list[str]
    valence: ValenceType
    intensity: IntensityType
    specificity: SpecificityType
    actionability: ActionabilityType
    temporal: TemporalType
    evidence: EvidenceType
    comparative: ComparativeType
    is_primary: bool
    confidence: ConfidenceType
    entity: str | None
    entity_type: EntityTypeValue | None
    relation_type: RelationType | None
    related_span_index: int | None
    usn: str


class LLMReviewSummary(TypedDict):
    """Review summary in the LLM response."""

    dominant_valence: ValenceType
    dominant_domain: str
    span_count: int
    has_comparative: bool
    has_entity: bool


class LLMClassificationResponse(TypedDict):
    """The full LLM classification response."""

    spans: list[LLMSpanResponse]
    review_summary: LLMReviewSummary
