"""
Embeddings Pydantic Schemas.

Type-safe models for the embeddings and semantic similarity system,
validated against schema/definitions/embeddings.yaml at runtime.

This module provides:
- EmbeddingsConfig: Runtime access to YAML configuration
- EmbeddingRequest: Request to generate embeddings
- EmbeddingResult: Result from embedding generation
- SimilarityMatch: A similarity search result
- DuplicateCandidate: A potential duplicate detection

Usage:
    from lightwave.schema.pydantic.models.embeddings import (
        EmbeddingsConfig,
        EmbeddingRequest,
        EmbeddingResult,
        SimilarityMatch,
    )

    # Check valid providers
    providers = EmbeddingsConfig.get_providers()

    # Create embedding request
    request = EmbeddingRequest(
        entity_type="task",
        entity_id="abc123",
        text="Implement user authentication",
    )
"""

from __future__ import annotations

from datetime import datetime
from functools import lru_cache
from pathlib import Path
from typing import Any

import yaml
from pydantic import Field, field_validator, model_validator

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema

# =============================================================================
# YAML CONFIGURATION LOADER
# =============================================================================


def _get_yaml_path() -> Path:
    """Get path to embeddings.yaml (from __index.yaml registry)."""
    from lightwave.schema.core.paths import get_sst_path

    return get_sst_path("embeddings")


@lru_cache(maxsize=1)
def _load_embeddings_yaml() -> dict[str, Any]:
    """Load and cache embeddings.yaml."""
    yaml_path = _get_yaml_path()
    if not yaml_path.exists():
        return {
            "providers": {"options": []},
            "entity_configs": {},
            "similarity_thresholds": {},
            "use_cases": {},
            "caching": {"enabled": False},
            "batch_processing": {"default_batch_size": 50},
            "vector_storage": {"options": []},
        }
    with open(yaml_path) as f:
        return yaml.safe_load(f)


class EmbeddingsConfig:
    """
    Runtime configuration loaded from embeddings.yaml.

    Provides type-safe access to YAML-defined values with caching.
    """

    @classmethod
    def _yaml(cls) -> dict[str, Any]:
        """Get cached YAML data."""
        return _load_embeddings_yaml()

    @classmethod
    def get_providers(cls) -> list[str]:
        """Get all valid provider names."""
        options = cls._yaml().get("providers", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_provider(cls, provider: str) -> bool:
        """Check if a provider is valid."""
        return provider in cls.get_providers()

    @classmethod
    def get_provider_config(cls, provider: str) -> dict[str, Any]:
        """Get configuration for a specific provider."""
        options = cls._yaml().get("providers", {}).get("options", [])
        for opt in options:
            if opt["value"] == provider:
                return opt
        return {}

    @classmethod
    def get_default_model(cls, provider: str) -> str | None:
        """Get the default model for a provider."""
        config = cls.get_provider_config(provider)
        models = config.get("models", [])
        for model in models:
            if model.get("default"):
                return model["value"]
        return models[0]["value"] if models else None

    @classmethod
    def get_model_dimensions(cls, provider: str, model: str) -> int:
        """Get embedding dimensions for a model."""
        config = cls.get_provider_config(provider)
        models = config.get("models", [])
        for m in models:
            if m["value"] == model:
                return m.get("dimensions", 1536)
        return 1536  # Default

    @classmethod
    def get_entity_types(cls) -> list[str]:
        """Get all entity types with embedding configs."""
        return list(cls._yaml().get("entity_configs", {}).keys())

    @classmethod
    def is_valid_entity_type(cls, entity_type: str) -> bool:
        """Check if an entity type has embedding config."""
        return entity_type in cls.get_entity_types()

    @classmethod
    def get_entity_config(cls, entity_type: str) -> dict[str, Any]:
        """Get embedding configuration for an entity type."""
        return cls._yaml().get("entity_configs", {}).get(entity_type, {})

    @classmethod
    def get_similarity_threshold(cls, use_case: str) -> float:
        """Get similarity threshold for a use case."""
        thresholds = cls._yaml().get("similarity_thresholds", {})
        if use_case in thresholds:
            return thresholds[use_case].get("threshold", 0.5)
        # Check use_cases for threshold_key reference
        use_cases = cls._yaml().get("use_cases", {})
        if use_case in use_cases:
            threshold_key = use_cases[use_case].get("threshold_key")
            if threshold_key and threshold_key in thresholds:
                return thresholds[threshold_key].get("threshold", 0.5)
        return 0.5

    @classmethod
    def get_use_case_config(cls, use_case: str) -> dict[str, Any]:
        """Get configuration for a use case."""
        return cls._yaml().get("use_cases", {}).get(use_case, {})

    @classmethod
    def get_batch_size(cls) -> int:
        """Get default batch size for embedding operations."""
        return cls._yaml().get("batch_processing", {}).get("default_batch_size", 50)

    @classmethod
    def get_cache_ttl(cls) -> int:
        """Get cache TTL in seconds."""
        return cls._yaml().get("caching", {}).get("ttl_seconds", 604800)

    @classmethod
    def is_caching_enabled(cls) -> bool:
        """Check if embedding caching is enabled."""
        return cls._yaml().get("caching", {}).get("enabled", True)

    @classmethod
    def clear_cache(cls) -> None:
        """Clear the YAML cache (for testing)."""
        _load_embeddings_yaml.cache_clear()


# =============================================================================
# EMBEDDING REQUEST
# =============================================================================


class EmbeddingRequest(LightwaveBaseSchema):
    """
    Request to generate an embedding for an entity.

    Attributes:
        entity_type: Type of entity (task, document, etc.)
        entity_id: Unique identifier for the entity
        text: Text to embed (combined from entity fields)
        provider: Embedding provider (openai, anthropic, local)
        model: Specific model to use
        metadata: Additional metadata
    """

    entity_type: str = Field(..., description="Entity type from embeddings.yaml")
    entity_id: str = Field(..., min_length=1, description="Entity identifier")
    text: str = Field(..., min_length=1, description="Text to embed")
    provider: str = Field("openai", description="Embedding provider")
    model: str | None = Field(None, description="Model override (uses default if None)")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")

    @field_validator("entity_type")
    @classmethod
    def validate_entity_type(cls, v: str) -> str:
        """Validate entity type against YAML."""
        if not EmbeddingsConfig.is_valid_entity_type(v):
            valid = EmbeddingsConfig.get_entity_types()
            raise ValueError(f"Invalid entity_type '{v}'. Valid types: {valid}")
        return v

    @field_validator("provider")
    @classmethod
    def validate_provider(cls, v: str) -> str:
        """Validate provider against YAML."""
        if not EmbeddingsConfig.is_valid_provider(v):
            valid = EmbeddingsConfig.get_providers()
            raise ValueError(f"Invalid provider '{v}'. Valid providers: {valid}")
        return v

    def get_model(self) -> str:
        """Get the model to use (explicit or default)."""
        if self.model:
            return self.model
        return EmbeddingsConfig.get_default_model(self.provider) or "text-embedding-3-small"


# =============================================================================
# EMBEDDING RESULT
# =============================================================================


class EmbeddingResult(LightwaveBaseSchema):
    """
    Result from embedding generation.

    Attributes:
        entity_type: Type of entity
        entity_id: Entity identifier
        embedding: The embedding vector
        dimensions: Number of dimensions
        provider: Provider used
        model: Model used
        tokens_used: Number of tokens processed
        cached: Whether this was from cache
        created_at: When the embedding was created
    """

    entity_type: str = Field(..., description="Entity type")
    entity_id: str = Field(..., description="Entity identifier")
    embedding: list[float] = Field(..., description="Embedding vector")
    dimensions: int = Field(..., ge=1, description="Vector dimensions")
    provider: str = Field(..., description="Provider used")
    model: str = Field(..., description="Model used")
    tokens_used: int = Field(0, ge=0, description="Tokens processed")
    cached: bool = Field(False, description="Whether from cache")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    text_hash: str = Field("", description="Hash of input text for cache invalidation")

    @model_validator(mode="after")
    def validate_embedding_dimensions(self) -> "EmbeddingResult":
        """Ensure embedding length matches dimensions."""
        if len(self.embedding) != self.dimensions:
            raise ValueError(f"Embedding length ({len(self.embedding)}) doesn't match dimensions ({self.dimensions})")
        return self


# =============================================================================
# SIMILARITY MATCH
# =============================================================================


class SimilarityMatch(LightwaveBaseSchema):
    """
    A match from similarity search.

    Attributes:
        entity_type: Type of matched entity
        entity_id: ID of matched entity
        score: Cosine similarity score (0-1)
        confidence: Confidence level based on thresholds
        label: Human-readable label for the entity
        metadata: Additional match metadata
    """

    entity_type: str = Field(..., description="Matched entity type")
    entity_id: str = Field(..., description="Matched entity ID")
    score: float = Field(..., ge=0.0, le=1.0, description="Similarity score")
    confidence: str = Field("low", description="Confidence level")
    label: str = Field("", description="Entity label")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Match metadata")

    @field_validator("confidence")
    @classmethod
    def validate_confidence(cls, v: str) -> str:
        """Validate confidence level."""
        valid = ["high", "medium", "low"]
        if v not in valid:
            raise ValueError(f"Invalid confidence '{v}'. Valid: {valid}")
        return v

    @classmethod
    def from_score(
        cls,
        entity_type: str,
        entity_id: str,
        score: float,
        label: str = "",
        metadata: dict[str, Any] | None = None,
    ) -> "SimilarityMatch":
        """Create a match with auto-computed confidence."""
        if score >= 0.85:
            confidence = "high"
        elif score >= 0.65:
            confidence = "medium"
        else:
            confidence = "low"

        return cls(
            entity_type=entity_type,
            entity_id=entity_id,
            score=score,
            confidence=confidence,
            label=label,
            metadata=metadata or {},
        )


# =============================================================================
# DUPLICATE CANDIDATE
# =============================================================================


class DuplicateCandidate(LightwaveBaseSchema):
    """
    A potential duplicate detection result.

    Attributes:
        source_type: Source entity type
        source_id: Source entity ID
        source_label: Source entity label
        duplicate_type: Duplicate entity type
        duplicate_id: Duplicate entity ID
        duplicate_label: Duplicate entity label
        similarity_score: How similar they are
        confidence: Confidence this is a true duplicate
        suggested_action: What to do about it
        metadata: Additional context
    """

    source_type: str = Field(..., description="Source entity type")
    source_id: str = Field(..., description="Source entity ID")
    source_label: str = Field("", description="Source entity label")
    duplicate_type: str = Field(..., description="Duplicate entity type")
    duplicate_id: str = Field(..., description="Duplicate entity ID")
    duplicate_label: str = Field("", description="Duplicate entity label")
    similarity_score: float = Field(..., ge=0.0, le=1.0, description="Similarity score")
    confidence: str = Field("medium", description="Duplicate confidence")
    suggested_action: str = Field("review", description="Suggested action")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Additional context")
    detected_at: datetime = Field(default_factory=datetime.utcnow, description="Detection time")

    @field_validator("suggested_action")
    @classmethod
    def validate_action(cls, v: str) -> str:
        """Validate suggested action."""
        valid = ["merge", "archive_duplicate", "review", "ignore"]
        if v not in valid:
            raise ValueError(f"Invalid action '{v}'. Valid: {valid}")
        return v


# =============================================================================
# SIMILARITY SEARCH REQUEST
# =============================================================================


class SimilaritySearchRequest(LightwaveBaseSchema):
    """
    Request for similarity search.

    Attributes:
        query_type: Type of entity to search for
        query_id: ID of query entity (if searching by entity)
        query_text: Text to search for (if searching by text)
        use_case: Predefined use case from YAML
        target_types: Entity types to search in
        max_results: Maximum results to return
        min_score: Minimum similarity score
        exclude_ids: Entity IDs to exclude
    """

    query_type: str | None = Field(None, description="Query entity type")
    query_id: str | None = Field(None, description="Query entity ID")
    query_text: str | None = Field(None, description="Query text")
    use_case: str = Field("suggest_related", description="Use case from YAML")
    target_types: list[str] = Field(default_factory=list, description="Target entity types")
    max_results: int = Field(10, ge=1, le=100, description="Max results")
    min_score: float | None = Field(None, ge=0.0, le=1.0, description="Min score override")
    exclude_ids: list[str] = Field(default_factory=list, description="IDs to exclude")

    @model_validator(mode="after")
    def validate_query_source(self) -> "SimilaritySearchRequest":
        """Ensure either query_id or query_text is provided."""
        if not self.query_id and not self.query_text:
            raise ValueError("Either query_id or query_text must be provided")
        return self

    def get_threshold(self) -> float:
        """Get similarity threshold for this request."""
        if self.min_score is not None:
            return self.min_score
        return EmbeddingsConfig.get_similarity_threshold(self.use_case)


# =============================================================================
# SIMILARITY SEARCH RESULT
# =============================================================================


class SimilaritySearchResult(LightwaveBaseSchema):
    """
    Result from similarity search.

    Attributes:
        query_type: Query entity type
        query_id: Query entity ID
        matches: List of similarity matches
        threshold_used: Similarity threshold used
        total_candidates: Total candidates searched
        search_time_ms: Search time in milliseconds
    """

    query_type: str | None = Field(None, description="Query entity type")
    query_id: str | None = Field(None, description="Query entity ID")
    matches: list[SimilarityMatch] = Field(default_factory=list, description="Matches")
    threshold_used: float = Field(..., description="Threshold used")
    total_candidates: int = Field(0, ge=0, description="Total candidates searched")
    search_time_ms: float = Field(0.0, ge=0.0, description="Search time")
    use_case: str = Field("", description="Use case applied")


# =============================================================================
# MODULE EXPORTS
# =============================================================================

__all__ = [
    "EmbeddingsConfig",
    "EmbeddingRequest",
    "EmbeddingResult",
    "SimilarityMatch",
    "DuplicateCandidate",
    "SimilaritySearchRequest",
    "SimilaritySearchResult",
]
