"""Data source definitions - top level for Level 1 indexing."""

from datetime import datetime
from enum import Enum

from pydantic import Field

from semantic_model.base import NamedModel, SemanticBaseModel, SourceId
from semantic_model.tables import Table
from semantic_model.relationships import InternalRelationship
from semantic_model.confidence import ConfidenceScore
from semantic_model.overrides import ExpertOverride


class SourceType(str, Enum):
    """Classification of data source types."""

    TRANSACTIONAL = "transactional"
    ANALYTICAL = "analytical"
    OPERATIONAL = "operational"
    EXTERNAL = "external"
    STAGING = "staging"
    DERIVED = "derived"


class SourceState(str, Enum):
    """State of a data source in the indexing workflow."""

    DISCOVERED = "discovered"
    INDEXING = "indexing"
    STAGING = "staging"
    CONFIRMED = "confirmed"
    DEPRECATED = "deprecated"
    ERROR = "error"


class SourceStatus(SemanticBaseModel):
    """Status of a data source in the indexing workflow."""

    state: SourceState = Field(default=SourceState.DISCOVERED)
    state_reason: str = Field(
        default="",
        description="Why the source is in this state",
    )
    moved_to_staging_at: datetime | None = Field(default=None)
    confirmed_at: datetime | None = Field(default=None)
    last_indexed_at: datetime | None = Field(default=None)
    pending_expert_review: bool = Field(default=False)
    expert_review_requested_at: datetime | None = Field(default=None)

    @property
    def is_confirmed(self) -> bool:
        """Check if source is confirmed for production use."""
        return self.state == SourceState.CONFIRMED

    @property
    def needs_review(self) -> bool:
        """Check if source needs expert review."""
        return self.pending_expert_review or self.state == SourceState.STAGING

    def start_indexing(self) -> "SourceStatus":
        """Mark source as being indexed."""
        return self.model_copy(
            update={
                "state": SourceState.INDEXING,
                "state_reason": "Indexing in progress",
            }
        )

    def move_to_staging(self, reason: str) -> "SourceStatus":
        """Move source to staging for expert review."""
        return self.model_copy(
            update={
                "state": SourceState.STAGING,
                "state_reason": reason,
                "moved_to_staging_at": datetime.utcnow(),
                "pending_expert_review": True,
                "expert_review_requested_at": datetime.utcnow(),
            }
        )

    def confirm(self) -> "SourceStatus":
        """Confirm source for production use."""
        return self.model_copy(
            update={
                "state": SourceState.CONFIRMED,
                "state_reason": "Confirmed after passing confidence threshold",
                "confirmed_at": datetime.utcnow(),
                "pending_expert_review": False,
                "last_indexed_at": datetime.utcnow(),
            }
        )

    def mark_error(self, reason: str) -> "SourceStatus":
        """Mark source as having an error."""
        return self.model_copy(
            update={
                "state": SourceState.ERROR,
                "state_reason": reason,
            }
        )


class ConnectionInfo(SemanticBaseModel):
    """Connection and sync information for a data source."""

    connector_type: str = Field(
        ...,
        description="Connector type: 'postgresql', 's3', 'kafka', etc.",
    )
    update_frequency: str = Field(
        default="unknown",
        description="How often data is updated: 'real-time', 'hourly', 'daily'",
    )
    typical_latency: str = Field(
        default="unknown",
        description="Typical data latency: '~5 minutes', '~1 hour'",
    )


class ProfilingMetadata(SemanticBaseModel):
    """Metadata about data profiling for a source."""

    last_profiled_at: datetime | None = Field(default=None)
    row_counts_available: bool = Field(default=False)
    sampling_enabled: bool = Field(default=False)
    sample_size: int | None = Field(default=None, ge=0)
    shuffled_copy_available: bool = Field(
        default=False,
        description="Whether a GDPR-safe shuffled version exists",
    )


class DependencyType(str, Enum):
    """Type of dependency between sources."""

    FOREIGN_KEY = "foreign_key"
    SEMANTIC_LINK = "semantic_link"
    DERIVED_FROM = "derived_from"


class DependencyStrength(str, Enum):
    """Strength of dependency (affects cascade reindexing)."""

    STRONG = "strong"
    WEAK = "weak"


class SourceDependency(SemanticBaseModel):
    """Tracks dependencies between data sources for cascade reindexing."""

    depends_on_source_id: SourceId = Field(
        ...,
        description="ID of the source this depends on",
    )
    dependency_type: DependencyType = Field(default=DependencyType.SEMANTIC_LINK)
    strength: DependencyStrength = Field(default=DependencyStrength.WEAK)
    linking_objects: list[str] = Field(
        default_factory=list,
        description="Table/column IDs that create this dependency",
    )


class DataSource(NamedModel):
    """A data source containing tables - the top-level Level 1 object."""

    # Trino location
    trino_catalog: str = Field(..., description="Trino catalog name")
    trino_schema: str = Field(..., description="Trino schema name")
    fully_qualified_prefix: str = Field(
        ...,
        description="Prefix for tables: 'catalog.schema'",
    )

    # Classification
    source_type: SourceType = Field(default=SourceType.ANALYTICAL)

    # Business context
    business_context: str = Field(
        default="",
        description="What business function this serves",
    )

    # Ownership
    owner_team: str = Field(default="")
    owner_contact: str = Field(default="")
    domain: str = Field(
        default="",
        description="Business domain: 'Sales', 'Finance', etc.",
    )

    # Technical metadata
    connection_info: ConnectionInfo | None = Field(default=None)

    # Status and confidence
    confidence: ConfidenceScore | None = Field(default=None)
    status: SourceStatus = Field(default_factory=SourceStatus)

    # Contents
    tables: list[Table] = Field(default_factory=list)

    # Internal relationships
    internal_relationships: list[InternalRelationship] = Field(default_factory=list)

    # Profiling metadata
    profiling: ProfilingMetadata = Field(default_factory=ProfilingMetadata)

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

    # Dependencies (for cascade reindexing)
    dependencies: list[SourceDependency] = Field(default_factory=list)

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

    @property
    def table_names(self) -> list[str]:
        """Get list of table names."""
        return [t.name for t in self.tables]

    @property
    def is_confirmed(self) -> bool:
        """Check if source is confirmed for production use."""
        return self.status.is_confirmed

    @property
    def needs_review(self) -> bool:
        """Check if source needs expert review."""
        return self.status.needs_review

    def get_table(self, name: str) -> Table | None:
        """Get a table by name."""
        for table in self.tables:
            if table.name == name:
                return table
        return None

    def get_table_by_id(self, table_id: str) -> Table | None:
        """Get a table by ID."""
        for table in self.tables:
            if table.id == table_id:
                return table
        return None

    def add_table(self, table: Table) -> "DataSource":
        """Add a table to this source."""
        return self.model_copy(update={"tables": [*self.tables, table]})

    def update_table(self, table: Table) -> "DataSource":
        """Update an existing table."""
        tables = [t if t.id != table.id else table for t in self.tables]
        return self.model_copy(update={"tables": tables})

    def check_confidence_threshold(self) -> tuple[bool, str]:
        """Check if source meets confidence threshold.

        Returns:
            Tuple of (meets_threshold, reason)
        """
        if self.confidence is None:
            return False, "No confidence score available"

        if self.confidence.meets_threshold:
            return True, "Meets confidence threshold"

        # Find why it doesn't meet threshold
        if self.confidence.low_confidence_items:
            items = self.confidence.low_confidence_items[:3]
            reasons = [f"{i.object_name}: {i.reason}" for i in items]
            return False, f"Low confidence items: {'; '.join(reasons)}"

        return False, f"Overall confidence {self.confidence.overall} below threshold {self.confidence.threshold}"

    def to_prompt_format(
        self,
        include_tables: bool = True,
        include_columns: bool = False,
    ) -> str:
        """Convert to a compact format for LLM prompts."""
        lines = [
            f"# Data Source: {self.name}",
            f"Location: {self.fully_qualified_prefix}",
            f"Type: {self.source_type.value}",
        ]

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

        if self.domain:
            lines.append(f"Domain: {self.domain}")

        if include_tables and self.tables:
            lines.append(f"\nTables ({self.table_count}):")
            for table in self.tables:
                lines.append(table.to_prompt_format(include_columns=include_columns))
                lines.append("")

        return "\n".join(lines)
