"""Pydantic models for Hierarchy-Driven Data Mart Factory.

Defines the contract types for mart configuration, pipeline generation,
formula precedence, and AI-driven discovery.
"""

from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class ReportType(str, Enum):
    """Supported financial report types."""
    PROFIT_AND_LOSS = "P&L"
    BALANCE_SHEET = "Balance Sheet"
    LINE_OF_SERVICE = "LOS"
    CUSTOM = "Custom"


class PipelineLayer(str, Enum):
    """Data mart pipeline object layers."""
    VW_1 = "VW_1"
    DT_2 = "DT_2"
    DT_3A = "DT_3A"
    DT_3 = "DT_3"


class ObjectType(str, Enum):
    """SQL object types generated by the pipeline."""
    VIEW = "VIEW"
    TABLE = "TABLE"


class QualityIssueLevel(str, Enum):
    """Severity levels for data quality issues."""
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"


# ---------------------------------------------------------------------------
# Configuration Models
# ---------------------------------------------------------------------------

class JoinPattern(BaseModel):
    """UNION ALL branch definition for DT_3A pre-aggregation.

    Each join pattern maps a named data source to its join/fact keys
    and optional row-level filter.
    """
    name: str = Field(..., description="Branch identifier (e.g., 'revenue', 'headcount')")
    join_keys: List[str] = Field(
        default_factory=list,
        description="Columns used to join to hierarchy dimensions",
    )
    fact_keys: List[str] = Field(
        default_factory=list,
        description="Measure/fact columns to aggregate",
    )
    source_table: str = Field("", description="Source table or view name")
    filter: Optional[str] = Field(
        None, description="Optional WHERE clause fragment for this branch"
    )


class DynamicColumnMapping(BaseModel):
    """Maps an ID_SOURCE value to its physical column in the source data.

    Used by DT_2 UNPIVOT logic to resolve which column holds values
    for each ID_SOURCE key.
    """
    id_source: str = Field(..., description="Logical identifier (e.g., 'REVENUE_USD')")
    physical_column: str = Field(..., description="Actual column name in source table")
    data_type: str = Field("NUMBER", description="Expected SQL data type")
    description: Optional[str] = None


class MartConfig(BaseModel):
    """Complete data mart configuration — the 7-variable contract.

    Captures all parameters needed to generate a 4-object DDL pipeline
    from a hierarchy definition.
    """
    project_name: str = Field(..., description="Unique mart project identifier")
    report_type: ReportType = Field(
        ReportType.PROFIT_AND_LOSS, description="Financial report type"
    )
    hierarchy_table: str = Field(
        "", description="Source hierarchy table/view name"
    )
    mapping_table: str = Field(
        "", description="ID_SOURCE mapping table/view name"
    )
    account_segment: str = Field(
        "GROSS", description="Account segment filter (GROSS/NET)"
    )
    measure_prefix: str = Field(
        "AMT_", description="Column name prefix for measure columns"
    )

    # Boolean flags (variables 5-7)
    has_sign_change: bool = Field(
        False, description="Enable SIGN_CHANGE_FLAG logic in DT_3A"
    )
    has_exclusions: bool = Field(
        False, description="Enable NOT IN subquery exclusions in DT_2"
    )
    has_group_filter_precedence: bool = Field(
        False, description="Enable multi-round group filter logic"
    )

    # Nested structures
    column_map: List[DynamicColumnMapping] = Field(
        default_factory=list,
        description="ID_SOURCE to physical column mappings",
    )
    join_patterns: List[JoinPattern] = Field(
        default_factory=list,
        description="UNION ALL branch definitions for DT_3A",
    )

    # Metadata
    schema_name: str = Field("ANALYTICS", description="Target schema")
    database_name: str = Field("", description="Target database")
    description: Optional[str] = None
    tags: List[str] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Pipeline Models
# ---------------------------------------------------------------------------

class PipelineObject(BaseModel):
    """Generated DDL object from the mart pipeline."""
    object_type: ObjectType = Field(..., description="VIEW or TABLE")
    object_name: str = Field(..., description="Fully qualified object name")
    layer: PipelineLayer = Field(..., description="Pipeline layer (VW_1..DT_3)")
    ddl: str = Field("", description="Generated SQL DDL statement")
    estimated_rows: Optional[int] = Field(
        None, description="Estimated row count (if known)"
    )
    depends_on: List[str] = Field(
        default_factory=list,
        description="Upstream object names this depends on",
    )


class MartPipeline(BaseModel):
    """Complete 4-object pipeline output."""
    project_name: str
    objects: List[PipelineObject] = Field(default_factory=list)
    generation_notes: List[str] = Field(default_factory=list)

    @property
    def total_objects(self) -> int:
        return len(self.objects)


# ---------------------------------------------------------------------------
# Formula Precedence Models
# ---------------------------------------------------------------------------

class FormulaPrecedence(BaseModel):
    """Calculation definition within the precedence cascade.

    Precedence levels 1-5 govern the order of calculation:
    - P1: Base aggregations (SUM of source data)
    - P2: Simple calculated rows (e.g., Gross Profit = Revenue - COGS)
    - P3: Ratios and percentages
    - P4: Variance calculations
    - P5: Complex derived metrics
    """
    precedence_level: int = Field(
        ..., ge=1, le=5, description="Calculation order (1=base, 5=complex)"
    )
    formula_group: str = Field(
        ..., description="Group name (e.g., 'Gross Profit')"
    )
    logic: str = Field(
        ..., description="Calculation expression (e.g., 'Revenue - COGS')"
    )
    param_ref: List[str] = Field(
        default_factory=list,
        description="Referenced formula groups this depends on",
    )
    output_column: str = Field(
        "", description="Target column for the result"
    )


class FormulaCascade(BaseModel):
    """Complete 5-level formula precedence cascade."""
    project_name: str
    formulas: List[FormulaPrecedence] = Field(default_factory=list)
    cascade_sql: str = Field("", description="Generated CTE cascade SQL")

    def by_level(self, level: int) -> List[FormulaPrecedence]:
        """Return formulas for a specific precedence level."""
        return [f for f in self.formulas if f.precedence_level == level]


# ---------------------------------------------------------------------------
# Discovery Models
# ---------------------------------------------------------------------------

class QualityIssue(BaseModel):
    """A data quality issue found during discovery."""
    level: QualityIssueLevel = QualityIssueLevel.WARNING
    message: str = ""
    column: Optional[str] = None
    suggestion: Optional[str] = None


class DiscoveryResult(BaseModel):
    """AI-driven hierarchy discovery output."""
    hierarchy_type: ReportType = Field(
        ReportType.CUSTOM, description="Detected report type"
    )
    level_count: int = Field(0, description="Number of hierarchy levels detected")
    detected_levels: List[str] = Field(
        default_factory=list, description="Level column names found"
    )
    suggestions: List[str] = Field(
        default_factory=list, description="Configuration suggestions"
    )
    quality_issues: List[QualityIssue] = Field(
        default_factory=list, description="Data quality issues found"
    )
    confidence: float = Field(
        ge=0.0, le=1.0, default=0.0, description="Overall detection confidence"
    )
    id_source_values: List[str] = Field(
        default_factory=list, description="Distinct ID_SOURCE values found"
    )
    recommended_join_patterns: List[JoinPattern] = Field(
        default_factory=list, description="Inferred UNION ALL branches"
    )


# ---------------------------------------------------------------------------
# Validation Models
# ---------------------------------------------------------------------------

class ValidationMessage(BaseModel):
    """Single validation check result."""
    check: str = Field(..., description="Name of the validation check")
    passed: bool = Field(True)
    message: str = ""
    severity: QualityIssueLevel = QualityIssueLevel.INFO


class MartValidationResult(BaseModel):
    """Aggregate validation result for config or pipeline."""
    valid: bool = True
    messages: List[ValidationMessage] = Field(default_factory=list)
    checked: int = Field(0, description="Number of checks run")
    passed: int = Field(0, description="Number of checks passed")

    @property
    def failed(self) -> int:
        return self.checked - self.passed
