"""Internal relationships within a data source."""

from enum import Enum

from pydantic import Field

from semantic_model.base import NamedModel, TableId, ColumnId
from semantic_model.overrides import ExpertOverride


class RelationshipType(str, Enum):
    """Cardinality type of a relationship."""

    ONE_TO_ONE = "one_to_one"
    ONE_TO_MANY = "one_to_many"
    MANY_TO_MANY = "many_to_many"


class DetectionMethod(str, Enum):
    """How the relationship was detected."""

    DECLARED_FK = "declared_fk"
    NAMING_CONVENTION = "naming_convention"
    VALUE_OVERLAP = "value_overlap"
    EXPERT_DEFINED = "expert_defined"


class JoinHint(str, Enum):
    """Recommended join type for this relationship."""

    INNER = "inner"
    LEFT = "left"
    RIGHT = "right"
    FULL = "full"


class InternalRelationship(NamedModel):
    """A relationship between tables within the same data source."""

    # Source side (the referencing table)
    from_table_id: TableId = Field(..., description="Table containing the foreign key")
    from_column_id: ColumnId = Field(..., description="Column that references another table")

    # Target side (the referenced table)
    to_table_id: TableId = Field(..., description="Referenced table")
    to_column_id: ColumnId = Field(..., description="Referenced column (usually PK)")

    # Relationship characteristics
    relationship_type: RelationshipType = Field(
        default=RelationshipType.MANY_TO_MANY,
        description="Cardinality of the relationship",
    )
    detection_method: DetectionMethod = Field(
        default=DetectionMethod.NAMING_CONVENTION,
        description="How this relationship was discovered",
    )

    # Join guidance
    join_hint: JoinHint = Field(
        default=JoinHint.LEFT,
        description="Recommended join type",
    )
    cardinality_notes: str = Field(
        default="",
        description="Notes about cardinality or join behavior",
    )

    # Confidence
    confidence: float = Field(
        default=0.5,
        ge=0.0,
        le=1.0,
        description="Confidence in this relationship",
    )

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

    @property
    def is_declared(self) -> bool:
        """Check if this is a declared (vs inferred) relationship."""
        return self.detection_method == DetectionMethod.DECLARED_FK

    @property
    def is_expert_defined(self) -> bool:
        """Check if this was defined by an expert."""
        return self.detection_method == DetectionMethod.EXPERT_DEFINED

    def to_join_clause(
        self,
        from_alias: str | None = None,
        to_alias: str | None = None,
    ) -> str:
        """Generate a SQL join clause for this relationship.
        
        Args:
            from_alias: Optional alias for the from table
            to_alias: Optional alias for the to table
            
        Returns:
            SQL join clause like "a.customer_id = b.id"
        """
        from_ref = f"{from_alias}." if from_alias else ""
        to_ref = f"{to_alias}." if to_alias else ""
        
        return f"{from_ref}{self.from_column_id} = {to_ref}{self.to_column_id}"

    def to_prompt_format(self) -> str:
        """Convert to a compact format for LLM prompts."""
        return (
            f"{self.from_table_id}.{self.from_column_id} -> "
            f"{self.to_table_id}.{self.to_column_id} "
            f"({self.relationship_type.value}, {self.join_hint.value} join)"
        )


def infer_relationship_type(
    from_is_pk: bool,
    to_is_pk: bool,
    from_is_unique: bool,
    to_is_unique: bool,
) -> RelationshipType:
    """Infer relationship type from column characteristics.
    
    Args:
        from_is_pk: Whether the from column is a primary key
        to_is_pk: Whether the to column is a primary key
        from_is_unique: Whether the from column has a unique constraint
        to_is_unique: Whether the to column has a unique constraint
        
    Returns:
        Inferred relationship type
    """
    if from_is_pk and to_is_pk:
        return RelationshipType.ONE_TO_ONE
    elif to_is_pk or to_is_unique:
        return RelationshipType.ONE_TO_MANY
    elif from_is_pk or from_is_unique:
        # Unusual case: FK from PK to non-unique column
        return RelationshipType.ONE_TO_MANY
    else:
        return RelationshipType.MANY_TO_MANY


def infer_join_hint(
    relationship_type: RelationshipType,
    from_nullable: bool,
    to_nullable: bool,
) -> JoinHint:
    """Infer recommended join type from relationship characteristics.
    
    Args:
        relationship_type: The relationship cardinality
        from_nullable: Whether the from column allows nulls
        to_nullable: Whether the to column allows nulls
        
        Returns:
        Recommended join hint
    """
    # If FK column is nullable, LEFT join preserves rows without matches
    if from_nullable:
        return JoinHint.LEFT
    
    # If both sides are non-null and it's many-to-one, INNER is usually fine
    if relationship_type == RelationshipType.ONE_TO_MANY and not from_nullable:
        return JoinHint.INNER
    
    # Default to LEFT for safety
    return JoinHint.LEFT
