"""Table definitions with semantic roles and temporal information."""

from datetime import datetime
from enum import Enum

from pydantic import Field

from semantic_model.base import (
    NamedModel,
    SemanticBaseModel,
    FullyQualifiedName,
    ColumnId,
)
from semantic_model.columns import Column
from semantic_model.confidence import ConfidenceScore
from semantic_model.overrides import ExpertOverride


class TableType(str, Enum):
    """Physical type of table."""

    TABLE = "table"
    VIEW = "view"
    MATERIALIZED_VIEW = "materialized_view"
    EXTERNAL_TABLE = "external_table"


class SemanticRole(str, Enum):
    """Semantic role of a table in the data model."""

    FACT = "fact"
    DIMENSION = "dimension"
    BRIDGE = "bridge"
    STAGING = "staging"
    AGGREGATE = "aggregate"
    SNAPSHOT = "snapshot"
    LOG = "log"
    LOOKUP = "lookup"
    UNKNOWN = "unknown"


class TemporalGrain(str, Enum):
    """Temporal grain of a table."""

    EVENT = "event"
    SNAPSHOT = "snapshot"
    SLOWLY_CHANGING = "slowly_changing"
    ACCUMULATING = "accumulating"
    STATIC = "static"


class TemporalInfo(SemanticBaseModel):
    """Temporal characteristics of a table."""

    grain: TemporalGrain = Field(
        default=TemporalGrain.STATIC,
        description="How time relates to rows in this table",
    )

    # Key timestamp columns
    primary_timestamp_column: ColumnId | None = Field(
        default=None,
        description="Main timestamp column",
    )
    event_time_column: ColumnId | None = Field(
        default=None,
        description="When the event occurred",
    )
    ingestion_time_column: ColumnId | None = Field(
        default=None,
        description="When data was loaded",
    )

    # For slowly changing dimensions
    valid_from_column: ColumnId | None = Field(default=None)
    valid_to_column: ColumnId | None = Field(default=None)

    # Timezone and partitioning
    timezone: str = Field(default="UTC")
    partition_column: ColumnId | None = Field(
        default=None,
        description="Column used for time-based partitioning",
    )

    @property
    def is_temporal(self) -> bool:
        """Check if this table has temporal characteristics."""
        return self.grain != TemporalGrain.STATIC


class PrimaryKey(SemanticBaseModel):
    """Primary key definition for a table."""

    columns: list[ColumnId] = Field(..., min_length=1)
    is_composite: bool = Field(default=False)
    is_surrogate: bool = Field(
        default=False,
        description="Whether this is an auto-generated key",
    )
    confidence: float = Field(default=1.0, ge=0.0, le=1.0)

    def __init__(self, **data):
        super().__init__(**data)
        # Auto-set is_composite based on column count
        if len(self.columns) > 1:
            object.__setattr__(self, "is_composite", True)


class TableProfiling(SemanticBaseModel):
    """Profiling results for a table."""

    row_count: int | None = Field(default=None, ge=0)
    row_count_as_of: datetime | None = Field(default=None)
    size_bytes: int | None = Field(default=None, ge=0)
    partition_count: int | None = Field(default=None, ge=0)
    last_modified_at: datetime | None = Field(default=None)
    is_empty: bool = Field(default=False)

    @property
    def size_mb(self) -> float | None:
        """Get size in megabytes."""
        if self.size_bytes is None:
            return None
        return round(self.size_bytes / (1024 * 1024), 2)

    @property
    def size_gb(self) -> float | None:
        """Get size in gigabytes."""
        if self.size_bytes is None:
            return None
        return round(self.size_bytes / (1024 * 1024 * 1024), 2)


class PatternType(str, Enum):
    """Types of query patterns."""

    FILTER = "filter"
    AGGREGATION = "aggregation"
    JOIN = "join"
    SORT = "sort"


class PatternFrequency(str, Enum):
    """Frequency of a query pattern."""

    COMMON = "common"
    OCCASIONAL = "occasional"
    RARE = "rare"


class QueryPattern(SemanticBaseModel):
    """A common query pattern for this table."""

    pattern_type: PatternType
    description: str
    example_sql: str = Field(default="")
    frequency: PatternFrequency = Field(default=PatternFrequency.OCCASIONAL)


class Table(NamedModel):
    """A table or view with semantic information."""

    # Identity
    fully_qualified_name: FullyQualifiedName = Field(
        ...,
        description="Full Trino path: catalog.schema.table",
    )

    # Classification
    table_type: TableType = Field(default=TableType.TABLE)
    semantic_role: SemanticRole = Field(
        default=SemanticRole.UNKNOWN,
        description="Role in the data model",
    )

    # Descriptions
    business_purpose: str = Field(
        default="",
        description="When and why to use this table",
    )
    usage_notes: str = Field(
        default="",
        description="Important considerations for using this table",
    )
    caveats: list[str] = Field(
        default_factory=list,
        description="Gotchas and known issues",
    )

    # Temporal
    temporal_info: TemporalInfo = Field(default_factory=TemporalInfo)

    # Schema
    columns: list[Column] = Field(default_factory=list)
    primary_key: PrimaryKey | None = Field(default=None)

    # Profiling
    profiling: TableProfiling | None = Field(default=None)

    # Confidence
    confidence: ConfidenceScore | None = Field(default=None)

    # Expert overrides
    expert_overrides: list[ExpertOverride] = Field(default_factory=list)

    # Query patterns
    common_patterns: list[QueryPattern] = Field(default_factory=list)

    @property
    def column_names(self) -> list[str]:
        """Get list of column names."""
        return [c.name for c in self.columns]

    @property
    def column_count(self) -> int:
        """Get number of columns."""
        return len(self.columns)

    def get_column(self, name: str) -> Column | None:
        """Get a column by name."""
        for col in self.columns:
            if col.name == name:
                return col
        return None

    def get_column_by_id(self, column_id: str) -> Column | None:
        """Get a column by ID."""
        for col in self.columns:
            if col.id == column_id:
                return col
        return None

    @property
    def key_columns(self) -> list[Column]:
        """Get primary key columns."""
        if not self.primary_key:
            return []
        return [col for col in self.columns if col.id in self.primary_key.columns or col.is_primary_key]

    @property
    def foreign_key_columns(self) -> list[Column]:
        """Get foreign key columns."""
        return [col for col in self.columns if col.is_foreign_key]

    @property
    def partition_columns(self) -> list[Column]:
        """Get partition key columns."""
        return [col for col in self.columns if col.is_partition_key]

    def to_prompt_format(self, include_columns: bool = True) -> str:
        """Convert to a compact format for LLM prompts."""
        lines = [
            f"## {self.name} ({self.semantic_role.value})",
            f"Path: {self.fully_qualified_name}",
        ]

        if self.description:
            lines.append(f"Description: {self.description}")

        if self.caveats:
            lines.append(f"Caveats: {'; '.join(self.caveats)}")

        if include_columns and self.columns:
            lines.append("\nColumns:")
            for col in self.columns:
                lines.append(col.to_prompt_format())

        return "\n".join(lines)

    def to_ddl(self) -> str:
        """Generate approximate DDL for this table."""
        lines = [f"CREATE TABLE {self.fully_qualified_name} ("]

        col_defs = []
        for col in self.columns:
            col_def = f"  {col.name} {col.data_type}"
            if not col.is_nullable:
                col_def += " NOT NULL"
            col_defs.append(col_def)

        lines.append(",\n".join(col_defs))

        if self.primary_key:
            pk_cols = ", ".join(self.primary_key.columns)
            lines.append(f",\n  PRIMARY KEY ({pk_cols})")

        lines.append(");")

        return "\n".join(lines)
