"""Column definitions with semantic types and profiling."""

from datetime import datetime
from enum import Enum

from pydantic import Field

from semantic_model.base import (
    NamedModel,
    SemanticBaseModel,
    TableId,
    ColumnId,
)
from semantic_model.confidence import ConfidenceScore
from semantic_model.overrides import ExpertOverride


class SemanticCategory(str, Enum):
    """High-level semantic categories for columns."""

    IDENTIFIER = "identifier"
    TIMESTAMP = "timestamp"
    CURRENCY = "currency"
    QUANTITY = "quantity"
    PERCENTAGE = "percentage"
    CATEGORICAL = "categorical"
    TEXT = "text"
    BOOLEAN = "boolean"
    GEOSPATIAL = "geospatial"
    JSON = "json"
    BINARY = "binary"
    UNKNOWN = "unknown"


class SemanticTypeInference(str, Enum):
    """How the semantic type was inferred."""

    COLUMN_NAME = "column_name"
    DATA_PATTERN = "data_pattern"
    VALUE_DISTRIBUTION = "value_distribution"
    FOREIGN_KEY = "foreign_key"
    EXPERT_OVERRIDE = "expert_override"


class SemanticType(SemanticBaseModel):
    """Semantic type information for a column."""

    category: SemanticCategory = Field(
        ...,
        description="High-level semantic category",
    )
    subtype: str | None = Field(
        default=None,
        description="More specific type: 'email', 'phone', 'uuid', 'country_code'",
    )
    inferred_from: SemanticTypeInference = Field(
        default=SemanticTypeInference.COLUMN_NAME,
        description="How this type was determined",
    )
    confidence: float = Field(
        default=0.5,
        ge=0.0,
        le=1.0,
        description="Confidence in this type assignment",
    )

    @classmethod
    def identifier(cls, subtype: str = "uuid", confidence: float = 0.8) -> "SemanticType":
        """Create an identifier semantic type."""
        return cls(
            category=SemanticCategory.IDENTIFIER,
            subtype=subtype,
            confidence=confidence,
        )

    @classmethod
    def timestamp(cls, subtype: str = "datetime", confidence: float = 0.9) -> "SemanticType":
        """Create a timestamp semantic type."""
        return cls(
            category=SemanticCategory.TIMESTAMP,
            subtype=subtype,
            confidence=confidence,
        )

    @classmethod
    def unknown(cls) -> "SemanticType":
        """Create an unknown semantic type."""
        return cls(
            category=SemanticCategory.UNKNOWN,
            confidence=0.0,
        )


class AllowedValue(SemanticBaseModel):
    """An allowed value for a categorical column."""

    value: str = Field(..., description="The actual value")
    description: str = Field(default="", description="Business meaning of this value")
    frequency: float = Field(
        default=0.0,
        ge=0.0,
        le=1.0,
        description="How common this value is (0-1)",
    )
    is_active: bool = Field(
        default=True,
        description="Whether this value is still in use",
    )


class ReferenceType(str, Enum):
    """Type of column reference."""

    FOREIGN_KEY = "foreign_key"
    INFERRED = "inferred"
    SOFT_REFERENCE = "soft_reference"


class ColumnReference(SemanticBaseModel):
    """Reference from this column to another table/column (intra-source FK)."""

    target_table_id: TableId
    target_column_id: ColumnId
    relationship_type: ReferenceType = Field(default=ReferenceType.INFERRED)
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)


class ValueFrequency(SemanticBaseModel):
    """Frequency of a specific value in a column."""

    value: str
    count: int = Field(ge=0)
    percentage: float = Field(ge=0.0, le=100.0)


class ColumnProfiling(SemanticBaseModel):
    """Profiling results for a column."""

    distinct_count: int | None = Field(default=None, ge=0)
    null_count: int | None = Field(default=None, ge=0)
    null_percentage: float | None = Field(default=None, ge=0.0, le=100.0)
    
    # For comparable types
    min_value: str | None = Field(default=None)
    max_value: str | None = Field(default=None)
    
    # For numeric types
    avg_value: float | None = Field(default=None)
    median_value: float | None = Field(default=None)
    std_dev: float | None = Field(default=None)
    
    # Value distribution
    most_common_values: list[ValueFrequency] = Field(
        default_factory=list,
        max_length=20,
    )
    
    # Pattern detection
    value_pattern: str | None = Field(
        default=None,
        description="Detected regex pattern for values",
    )
    
    profiled_at: datetime | None = Field(default=None)

    @property
    def is_high_cardinality(self) -> bool:
        """Check if column has high cardinality (many distinct values)."""
        return self.distinct_count is not None and self.distinct_count > 1000

    @property
    def is_mostly_null(self) -> bool:
        """Check if column is mostly null (>90%)."""
        return self.null_percentage is not None and self.null_percentage > 90.0


class SampleValue(SemanticBaseModel):
    """A sample value from the column."""

    value: str
    is_shuffled: bool = Field(
        default=False,
        description="Whether this is from a GDPR-safe shuffled copy",
    )
    context: str = Field(
        default="",
        description="Optional context about this sample",
    )


class Column(NamedModel):
    """A column in a table with semantic information."""

    # Position
    ordinal_position: int = Field(ge=0, description="Column position in table")

    # Technical type
    data_type: str = Field(..., description="Trino SQL type (e.g., VARCHAR, INTEGER)")
    is_nullable: bool = Field(default=True)
    default_value: str | None = Field(default=None)

    # Semantic type
    semantic_type: SemanticType = Field(
        default_factory=SemanticType.unknown,
        description="Inferred semantic meaning",
    )

    # Business description
    business_meaning: str = Field(
        default="",
        description="What this column represents in business terms",
    )

    # Role flags
    is_primary_key: bool = Field(default=False)
    is_partition_key: bool = Field(default=False)
    is_clustering_key: bool = Field(default=False)

    # Categorical columns
    is_categorical: bool = Field(
        default=False,
        description="Whether this column has a finite set of values",
    )
    allowed_values: list[AllowedValue] = Field(
        default_factory=list,
        description="Allowed values if categorical",
    )

    # Numeric columns
    unit: str | None = Field(
        default=None,
        description="Unit of measurement: 'USD', 'seconds', 'percentage'",
    )
    precision_notes: str = Field(
        default="",
        description="Notes about numeric precision",
    )

    # Reference columns
    is_foreign_key: bool = Field(default=False)
    references: ColumnReference | None = Field(
        default=None,
        description="What this column references (within source)",
    )

    # Derived columns
    is_derived: bool = Field(default=False)
    derivation_logic: str = Field(
        default="",
        description="How this column is calculated",
    )

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

    # Samples
    sample_values: list[SampleValue] = Field(
        default_factory=list,
        max_length=10,
    )

    # Common usage patterns
    common_filters: list[str] = Field(
        default_factory=list,
        description="Common filter patterns for this column",
    )

    # Sensitivity
    is_sensitive: bool = Field(
        default=False,
        description="Whether this column contains PII or sensitive data",
    )

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

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

    @property
    def fully_qualified_type(self) -> str:
        """Get a descriptive type string including semantic info."""
        base = self.data_type
        if self.semantic_type.category != SemanticCategory.UNKNOWN:
            base = f"{base} ({self.semantic_type.category.value}"
            if self.semantic_type.subtype:
                base = f"{base}:{self.semantic_type.subtype}"
            base = f"{base})"
        return base

    def to_prompt_format(self) -> str:
        """Convert to a compact format for LLM prompts."""
        parts = [f"- {self.name}: {self.data_type}"]
        
        if self.description:
            parts.append(f"  # {self.description}")
        
        flags = []
        if self.is_primary_key:
            flags.append("PK")
        if self.is_partition_key:
            flags.append("PARTITION")
        if self.is_foreign_key:
            flags.append("FK")
        if self.is_nullable:
            flags.append("NULL")
        
        if flags:
            parts[0] += f" [{', '.join(flags)}]"
        
        if self.unit:
            parts.append(f"  unit: {self.unit}")
        
        if self.allowed_values:
            values = [v.value for v in self.allowed_values[:5]]
            parts.append(f"  values: {values}")
        
        return "\n".join(parts)
