"""Serialization utilities for loading and saving semantic models."""

import json
from pathlib import Path
from typing import Any

from semantic_model.model import SemanticModel


def model_to_dict(model: SemanticModel) -> dict[str, Any]:
    """Convert a SemanticModel to a dictionary.

    Args:
        model: The semantic model to convert

    Returns:
        Dictionary representation of the model
    """
    return model.model_dump(mode="json")


def load_model_from_dict(data: dict[str, Any]) -> SemanticModel:
    """Load a SemanticModel from a dictionary.

    Args:
        data: Dictionary representation of the model

    Returns:
        Parsed SemanticModel
    """
    return SemanticModel.model_validate(data)


def save_model(
    model: SemanticModel,
    path: str | Path,
    indent: int = 2,
) -> None:
    """Save a SemanticModel to a JSON file.

    Args:
        model: The semantic model to save
        path: File path to save to
        indent: JSON indentation (default 2)
    """
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)

    data = model_to_dict(model)

    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=indent, ensure_ascii=False, default=str)


def load_model(path: str | Path) -> SemanticModel:
    """Load a SemanticModel from a JSON file.

    Args:
        path: File path to load from

    Returns:
        Parsed SemanticModel
    """
    path = Path(path)

    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)

    return load_model_from_dict(data)


def save_model_yaml(
    model: SemanticModel,
    path: str | Path,
) -> None:
    """Save a SemanticModel to a YAML file.

    Requires PyYAML to be installed.

    Args:
        model: The semantic model to save
        path: File path to save to
    """
    try:
        import yaml
    except ImportError:
        raise ImportError("PyYAML is required for YAML serialization. Install with: pip install pyyaml")

    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)

    data = model_to_dict(model)

    with open(path, "w", encoding="utf-8") as f:
        yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)


def load_model_yaml(path: str | Path) -> SemanticModel:
    """Load a SemanticModel from a YAML file.

    Requires PyYAML to be installed.

    Args:
        path: File path to load from

    Returns:
        Parsed SemanticModel
    """
    try:
        import yaml
    except ImportError:
        raise ImportError("PyYAML is required for YAML serialization. Install with: pip install pyyaml")

    path = Path(path)

    with open(path, "r", encoding="utf-8") as f:
        data = yaml.safe_load(f)

    return load_model_from_dict(data)


class ModelExporter:
    """Export semantic models to various formats."""

    def __init__(self, model: SemanticModel):
        self.model = model

    def to_json(self, indent: int = 2) -> str:
        """Export to JSON string."""
        return json.dumps(
            model_to_dict(self.model),
            indent=indent,
            ensure_ascii=False,
            default=str,
        )

    def to_prompt_context(
        self,
        include_columns: bool = True,
        max_tokens: int | None = None,
    ) -> str:
        """Export to a format optimized for LLM context injection.

        Args:
            include_columns: Whether to include column details
            max_tokens: Approximate max tokens (will truncate if needed)

        Returns:
            Prompt-ready string
        """
        full_context = self.model.to_prompt_format(
            include_sources=True,
            include_entities=True,
            include_glossary=True,
            include_columns=include_columns,
        )

        if max_tokens is not None:
            # Rough estimate: 1 token ≈ 4 characters
            max_chars = max_tokens * 4
            if len(full_context) > max_chars:
                full_context = full_context[:max_chars] + "\n\n... (truncated)"

        return full_context

    def to_source_summary(self) -> dict[str, Any]:
        """Export a summary of sources for quick reference."""
        return {
            "sources": [
                {
                    "id": s.id,
                    "name": s.name,
                    "prefix": s.fully_qualified_prefix,
                    "type": s.source_type.value,
                    "status": s.status.state.value,
                    "table_count": s.table_count,
                    "tables": s.table_names,
                }
                for s in self.model.sources
            ]
        }

    def to_entity_summary(self) -> dict[str, Any]:
        """Export a summary of entities for quick reference."""
        return {
            "entities": [
                {
                    "id": e.id,
                    "name": e.name,
                    "canonical_id": e.canonical_id_name,
                    "sources": e.source_ids,
                    "attribute_count": len(e.unified_attributes),
                }
                for e in self.model.entities
            ]
        }


class ModelDiff:
    """Compare two semantic models and find differences."""

    def __init__(self, old_model: SemanticModel, new_model: SemanticModel):
        self.old = old_model
        self.new = new_model

    def get_source_changes(self) -> dict[str, list[str]]:
        """Get source-level changes between models."""
        old_ids = {s.id for s in self.old.sources}
        new_ids = {s.id for s in self.new.sources}

        return {
            "added": list(new_ids - old_ids),
            "removed": list(old_ids - new_ids),
            "common": list(old_ids & new_ids),
        }

    def get_entity_changes(self) -> dict[str, list[str]]:
        """Get entity-level changes between models."""
        old_ids = {e.id for e in self.old.entities}
        new_ids = {e.id for e in self.new.entities}

        return {
            "added": list(new_ids - old_ids),
            "removed": list(old_ids - new_ids),
            "common": list(old_ids & new_ids),
        }

    def has_changes(self) -> bool:
        """Check if there are any changes between models."""
        source_changes = self.get_source_changes()
        entity_changes = self.get_entity_changes()

        return bool(
            source_changes["added"] or source_changes["removed"] or entity_changes["added"] or entity_changes["removed"]
        )
