"""Schema versioning and compatibility checking for MCP Vector Search.

This module ensures that the database schema matches the code version,
preventing infinite retry loops when fields are missing or incompatible.
"""

import json
from datetime import UTC
from pathlib import Path

from loguru import logger

from mcp_vector_search import __version__

# Schema version matches package version
SCHEMA_VERSION = __version__

# Required fields for each table/collection
# These are the fields that MUST exist in the database for the current code version
REQUIRED_FIELDS = {
    "chunks": [
        "chunk_id",
        "file_path",
        "content",
        "start_line",
        "end_line",
        "language",
        "chunk_type",
        "function_name",
        "class_name",
        "docstring",
        "imports",
        "calls",  # Added in 2.3.x
        "inherits_from",  # Added in 2.3.x
    ],
    "vectors": [
        "chunk_id",
        "vector",
    ],
}

# Optional fields that can be missing without breaking functionality
OPTIONAL_FIELDS = {
    "chunks": [
        "complexity_score",
        "parent_chunk_id",
        "child_chunk_ids",
        "chunk_depth",
        "decorators",
        "parameters",
        "return_type",
        "type_annotations",
        "subproject_name",
        "subproject_path",
        "nlp_keywords",
        "nlp_code_refs",
        "nlp_technical_terms",
    ],
}


class SchemaVersion:
    """Schema version information and comparison."""

    def __init__(self, version_str: str) -> None:
        """Parse version string (e.g., "2.3.4")."""
        self.version_str = version_str
        parts = version_str.split(".")
        self.major = int(parts[0]) if len(parts) > 0 else 0
        self.minor = int(parts[1]) if len(parts) > 1 else 0
        self.patch = int(parts[2]) if len(parts) > 2 else 0

    def is_compatible_with(self, other: "SchemaVersion") -> bool:
        """Check if two versions are compatible.

        Compatible means:
        - Same major version (breaking changes between major versions)
        - Database version <= code version (code can handle older schemas)
        """
        if self.major != other.major:
            return False

        # Database version must be <= code version
        if self.minor > other.minor:
            return False
        if self.minor == other.minor and self.patch > other.patch:
            return False

        return True

    def __str__(self) -> str:
        return self.version_str

    def __repr__(self) -> str:
        return f"SchemaVersion('{self.version_str}')"


def get_schema_version_path(db_path: Path) -> Path:
    """Get path to schema version file.

    Args:
        db_path: Database directory path (e.g., .mcp-vector-search/lancedb)

    Returns:
        Path to schema_version.json
    """
    # Store version in parent directory (.mcp-vector-search)
    mcp_dir = db_path.parent if db_path.name == "lancedb" else db_path
    return mcp_dir / "schema_version.json"


def load_schema_version(db_path: Path) -> SchemaVersion | None:
    """Load schema version from database directory.

    Args:
        db_path: Database directory path

    Returns:
        SchemaVersion if exists, None if not found
    """
    version_file = get_schema_version_path(db_path)

    if not version_file.exists():
        logger.debug(f"No schema version file found at {version_file}")
        return None

    try:
        with open(version_file) as f:
            data = json.load(f)
        version_str = data.get("version", "0.0.0")
        return SchemaVersion(version_str)
    except Exception as e:
        logger.warning(f"Failed to load schema version: {e}")
        return None


def save_schema_version(db_path: Path, version: SchemaVersion | None = None) -> None:
    """Save schema version to database directory.

    Args:
        db_path: Database directory path
        version: Version to save (defaults to current SCHEMA_VERSION)
    """
    if version is None:
        version = SchemaVersion(SCHEMA_VERSION)

    version_file = get_schema_version_path(db_path)

    try:
        # Ensure parent directory exists
        version_file.parent.mkdir(parents=True, exist_ok=True)

        data = {
            "version": str(version),
            "updated_at": _get_timestamp(),
        }

        with open(version_file, "w") as f:
            json.dump(data, f, indent=2)

        logger.debug(f"Saved schema version {version} to {version_file}")
    except Exception as e:
        logger.error(f"Failed to save schema version: {e}")


def check_schema_compatibility(db_path: Path) -> tuple[bool, str]:
    """Check if database schema matches current code version.

    Args:
        db_path: Database directory path

    Returns:
        Tuple of (is_compatible, message)
        - is_compatible: True if schema is compatible
        - message: Human-readable status message
    """
    current_version = SchemaVersion(SCHEMA_VERSION)
    db_version = load_schema_version(db_path)

    if db_version is None:
        # No version file - assume old database (pre-2.3.x)
        return (
            False,
            f"No schema version found. Database may be from older version (< 2.3.0).\n"
            f"Current code version: {current_version}\n"
            f"Recommendation: Reset database and reindex.",
        )

    if db_version.is_compatible_with(current_version):
        return (
            True,
            f"Schema version {db_version} is compatible with code version {current_version}",
        )

    # Incompatible versions
    if db_version.major != current_version.major:
        return (
            False,
            f"❌ Schema version mismatch!\n\n"
            f"Database version: {db_version}\n"
            f"Code version: {current_version}\n\n"
            f"Major version mismatch - database schema is incompatible.\n"
            f"Recommendation: Reset database with --force flag.",
        )

    # Same major version but database is newer than code
    if db_version.minor > current_version.minor or (
        db_version.minor == current_version.minor
        and db_version.patch > current_version.patch
    ):
        return (
            False,
            f"⚠️  Database schema is newer than code!\n\n"
            f"Database version: {db_version}\n"
            f"Code version: {current_version}\n\n"
            f"Recommendation: Upgrade mcp-vector-search to latest version:\n"
            f"  pip install --upgrade mcp-vector-search",
        )

    return (
        True,
        f"Schema compatible (database: {db_version}, code: {current_version})",
    )


def get_missing_fields(db_path: Path) -> dict[str, list[str]]:
    """Get fields that exist in code but not in database.

    This is a placeholder - actual implementation would need to inspect
    the LanceDB/ChromaDB schema to detect missing columns.

    Args:
        db_path: Database directory path

    Returns:
        Dict mapping table names to lists of missing field names
    """
    # For now, return empty dict - field detection requires database inspection
    # This would be implemented by:
    # 1. Opening the LanceDB table
    # 2. Getting schema via table.schema
    # 3. Comparing with REQUIRED_FIELDS
    logger.debug("Field inspection not yet implemented")
    return {}


def _get_timestamp() -> str:
    """Get current timestamp in ISO format."""
    from datetime import datetime

    return datetime.now(UTC).isoformat()


def migrate_schema(db_path: Path, force: bool = False) -> tuple[bool, str]:
    """Attempt to migrate schema to current version.

    For LanceDB, column addition is challenging because:
    - LanceDB uses Apache Arrow schemas which are immutable
    - Adding columns requires recreating the entire table

    Therefore, this function primarily serves to:
    1. Detect incompatibilities
    2. Recommend reset/reindex
    3. Save updated schema version after successful reset

    Args:
        db_path: Database directory path
        force: If True, overwrite existing schema version

    Returns:
        Tuple of (success, message)
    """
    current_version = SchemaVersion(SCHEMA_VERSION)

    if not force:
        is_compatible, msg = check_schema_compatibility(db_path)
        if not is_compatible:
            return (
                False,
                f"Schema migration required:\n{msg}\n\n"
                f"Use --force flag to reset database and reindex.",
            )

    # If we reach here, either force=True or schema is compatible
    save_schema_version(db_path, current_version)
    return (True, f"Schema version updated to {current_version}")
