"""Semantic entities - cross-source business concepts (Level 2)."""

from enum import Enum

from pydantic import Field

from semantic_model.base import (
    NamedModel,
    SemanticBaseModel,
    SourceId,
    TableId,
    ColumnId,
    FullyQualifiedName,
    SqlExpression,
)
from semantic_model.columns import SemanticType
from semantic_model.confidence import ConfidenceScore
from semantic_model.overrides import ExpertOverride


class ManifestationRole(str, Enum):
    """Role of a manifestation in representing an entity."""

    PRIMARY = "primary"
    REPLICA = "replica"
    DERIVED = "derived"
    PARTIAL = "partial"
    HISTORICAL = "historical"
    EVENT_LOG = "event_log"
    LOOKUP = "lookup"


class KeyMappingType(str, Enum):
    """How keys map between manifestations."""

    IDENTICAL = "identical"
    PREFIXED = "prefixed"
    TRANSFORMED = "transformed"
    LOOKUP_REQUIRED = "lookup_required"


class KeyMapping(SemanticBaseModel):
    """How to map keys between manifestations of an entity."""

    mapping_type: KeyMappingType = Field(default=KeyMappingType.IDENTICAL)
    transformation_expression: SqlExpression | None = Field(
        default=None,
        description="SQL expression for transformation",
    )
    lookup_table: FullyQualifiedName | None = Field(
        default=None,
        description="Mapping table if lookup required",
    )
    lookup_from_column: str | None = Field(default=None)
    lookup_to_column: str | None = Field(default=None)
    notes: str = Field(default="")


class AttributeQuality(str, Enum):
    """Quality level of an attribute source."""

    AUTHORITATIVE = "authoritative"
    RELIABLE = "reliable"
    DERIVED = "derived"
    STALE = "stale"
    APPROXIMATE = "approximate"


class AttributeContribution(SemanticBaseModel):
    """Maps a manifestation's column to a unified attribute."""

    unified_attribute_id: str
    source_column_id: ColumnId
    transformation: SqlExpression | None = Field(
        default=None,
        description="SQL transformation if needed",
    )
    quality: AttributeQuality = Field(default=AttributeQuality.RELIABLE)
    priority: int = Field(
        default=1,
        ge=1,
        description="Lower = preferred source",
    )


class SyncMethod(str, Enum):
    """How a manifestation is synchronized."""

    REAL_TIME = "real_time"
    STREAMING = "streaming"
    BATCH = "batch"
    MANUAL = "manual"
    DERIVED = "derived"


class SyncInfo(SemanticBaseModel):
    """Synchronization information for a manifestation."""

    method: SyncMethod = Field(default=SyncMethod.BATCH)
    latency: str = Field(default="unknown")
    schedule: str = Field(default="")


class EntityManifestation(SemanticBaseModel):
    """Where a semantic entity physically exists in a data source."""

    # Location
    source_id: SourceId
    table_id: TableId
    fully_qualified_name: FullyQualifiedName

    # Role
    role: ManifestationRole = Field(default=ManifestationRole.PARTIAL)

    # Identity
    key_column_id: ColumnId = Field(
        ...,
        description="Column that identifies the entity in this table",
    )
    key_mapping: KeyMapping = Field(default_factory=KeyMapping)

    # Attribute contributions
    attribute_contributions: list[AttributeContribution] = Field(default_factory=list)

    # Sync info
    sync_info: SyncInfo = Field(default_factory=SyncInfo)

    # Usage guidance
    usage_guidance: str = Field(
        default="",
        description="When to use this manifestation",
    )

    # Confidence
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)

    @property
    def is_primary(self) -> bool:
        """Check if this is the primary manifestation."""
        return self.role == ManifestationRole.PRIMARY


class ResolutionStrategy(str, Enum):
    """Strategy for resolving attribute values from multiple sources."""

    PREFER_PRIMARY = "prefer_primary"
    MOST_RECENT = "most_recent"
    COALESCE = "coalesce"
    CUSTOM = "custom"


class AttributeSource(SemanticBaseModel):
    """A source for a unified attribute."""

    source_id: SourceId
    table_id: TableId
    column_id: ColumnId
    fully_qualified_column: str = Field(
        ...,
        description="catalog.schema.table.column",
    )
    transformation: SqlExpression | None = Field(default=None)
    priority: int = Field(default=1, ge=1)
    freshness: str = Field(default="unknown")
    quality_notes: str = Field(default="")


class UnifiedAttribute(NamedModel):
    """A logical attribute of an entity that may come from multiple sources."""

    # Type
    semantic_type: SemanticType

    # Sources (in priority order)
    sources: list[AttributeSource] = Field(
        default_factory=list,
        description="Where this attribute can be sourced from",
    )

    # Resolution
    resolution_strategy: ResolutionStrategy = Field(
        default=ResolutionStrategy.PREFER_PRIMARY,
    )
    resolution_expression: SqlExpression | None = Field(
        default=None,
        description="SQL expression for custom resolution",
    )

    # Confidence
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)

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

    @property
    def primary_source(self) -> AttributeSource | None:
        """Get the highest priority source."""
        if not self.sources:
            return None
        return min(self.sources, key=lambda s: s.priority)


class IdentityResolutionStrategy(str, Enum):
    """Strategy for resolving entity identity across sources."""

    SINGLE_KEY = "single_key"
    MAPPING_TABLE = "mapping_table"
    COMPOSITE_MATCH = "composite_match"
    FUZZY_MATCH = "fuzzy_match"
    HIERARCHY = "hierarchy"


class MappingColumn(SemanticBaseModel):
    """Column in a mapping table that maps source IDs."""

    source_id: SourceId
    column_name: str = Field(
        ...,
        description="Column in mapping table",
    )
    maps_to_source_key: str = Field(
        ...,
        description="Which source's key this maps to",
    )


class MatchType(str, Enum):
    """Type of matching for composite/fuzzy identity resolution."""

    EXACT = "exact"
    FUZZY = "fuzzy"
    PHONETIC = "phonetic"
    NORMALIZED = "normalized"


class MatchField(SemanticBaseModel):
    """A field used for composite/fuzzy matching."""

    unified_attribute_id: str
    match_type: MatchType = Field(default=MatchType.EXACT)
    weight: float = Field(default=1.0, ge=0.0)


class IdentityResolution(SemanticBaseModel):
    """How to resolve entity identity across sources."""

    strategy: IdentityResolutionStrategy = Field(
        default=IdentityResolutionStrategy.SINGLE_KEY,
    )

    # For mapping table strategy
    mapping_table: FullyQualifiedName | None = Field(default=None)
    mapping_columns: list[MappingColumn] = Field(default_factory=list)

    # For composite/fuzzy match
    match_fields: list[MatchField] = Field(default_factory=list)
    match_threshold: float = Field(
        default=0.9,
        ge=0.0,
        le=1.0,
        description="Threshold for fuzzy matching",
    )

    # Notes
    resolution_notes: str = Field(default="")
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)


class LifecycleState(SemanticBaseModel):
    """A state in the entity lifecycle."""

    value: str = Field(..., description="The actual value in the data")
    name: str = Field(..., description="Human-readable name")
    description: str = Field(default="")
    is_active: bool = Field(
        default=True,
        description="Whether entity is considered active in this state",
    )
    is_terminal: bool = Field(
        default=False,
        description="Whether this is a final state",
    )
    typical_duration: str = Field(default="")


class DeletedStrategy(str, Enum):
    """Strategy for handling deleted entities."""

    HARD_DELETE = "hard_delete"
    SOFT_DELETE = "soft_delete"
    STATUS_FLAG = "status_flag"
    ARCHIVE = "archive"


class EntityLifecycle(SemanticBaseModel):
    """Lifecycle information for an entity."""

    states: list[LifecycleState] = Field(default_factory=list)
    state_source: SourceId | None = Field(
        default=None,
        description="Which source tracks state",
    )
    state_column_id: ColumnId | None = Field(default=None)
    created_at_column_id: ColumnId | None = Field(default=None)
    updated_at_column_id: ColumnId | None = Field(default=None)
    deleted_strategy: DeletedStrategy = Field(default=DeletedStrategy.SOFT_DELETE)
    deleted_indicator: str = Field(
        default="",
        description="Column or condition indicating deletion",
    )


class EntityStatus(str, Enum):
    """Status of a semantic entity."""

    DRAFT = "draft"
    STAGING = "staging"
    CONFIRMED = "confirmed"
    DEPRECATED = "deprecated"


class SemanticEntity(NamedModel):
    """A canonical business concept that spans multiple data sources."""

    # Business context
    business_context: str = Field(
        default="",
        description="Role of this entity in the organization",
    )

    # Domain
    domain: str = Field(
        default="",
        description="Primary business domain",
    )

    # Canonical identifier
    canonical_id_name: str = Field(
        ...,
        description="Name of the canonical ID: 'customer_id'",
    )
    canonical_id_format: str = Field(
        default="",
        description="Format of the ID: 'UUID', 'CUST-NNNNNN'",
    )
    canonical_id_description: str = Field(default="")

    # Manifestations
    manifestations: list[EntityManifestation] = Field(default_factory=list)

    # Unified attributes
    unified_attributes: list[UnifiedAttribute] = Field(default_factory=list)

    # Identity resolution
    identity_resolution: IdentityResolution = Field(default_factory=IdentityResolution)

    # Lifecycle
    lifecycle: EntityLifecycle | None = Field(default=None)

    # Status
    status: EntityStatus = Field(default=EntityStatus.DRAFT)

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

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

    # Common queries
    common_queries: list[str] = Field(
        default_factory=list,
        description="Common questions about this entity",
    )

    @property
    def primary_manifestation(self) -> EntityManifestation | None:
        """Get the primary manifestation of this entity."""
        for m in self.manifestations:
            if m.is_primary:
                return m
        return None

    @property
    def source_ids(self) -> list[SourceId]:
        """Get all source IDs where this entity appears."""
        return list(set(m.source_id for m in self.manifestations))

    def get_manifestation(self, source_id: SourceId) -> EntityManifestation | None:
        """Get manifestation for a specific source."""
        for m in self.manifestations:
            if m.source_id == source_id:
                return m
        return None

    def get_attribute(self, name: str) -> UnifiedAttribute | None:
        """Get a unified attribute by name."""
        for attr in self.unified_attributes:
            if attr.name == name:
                return attr
        return None

    def to_prompt_format(self) -> str:
        """Convert to a compact format for LLM prompts."""
        lines = [
            f"# Entity: {self.name}",
            f"Canonical ID: {self.canonical_id_name}",
        ]
        
        if self.description:
            lines.append(f"Description: {self.description}")
        
        # Primary manifestation
        primary = self.primary_manifestation
        if primary:
            lines.append(f"Primary source: {primary.fully_qualified_name}")
        
        # Other manifestations
        others = [m for m in self.manifestations if not m.is_primary]
        if others:
            lines.append("Also found in:")
            for m in others:
                lines.append(f"  - {m.fully_qualified_name} ({m.role.value})")
        
        # Key attributes
        if self.unified_attributes:
            lines.append("Key attributes:")
            for attr in self.unified_attributes[:10]:
                lines.append(f"  - {attr.name}: {attr.description}")
        
        return "\n".join(lines)
