"""
Unified MCP tools for Data Versioning (Phase 3D-1 consolidation).

Consolidates 12 individual versioning tools into 3 action-dispatched tools:
- version_manage(action, ...) — 6 actions: create, create_hierarchy, create_catalog_asset, get, list, stats
- version_diff(action, ...)   — 4 actions: diff, diff_latest, preview_rollback, rollback
- version_meta(action, ...)   — 2 actions: tag, search

Original tool names are preserved as deprecation wrappers in mcp_tools.py.
Note: This module uses mcp_only pattern (no settings parameter in dispatch).
"""

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from .types import VersionedObjectType, ChangeType, VersionQuery
from .version_store import VersionStore
from .version_manager import VersionManager

logger = logging.getLogger(__name__)

# ============================================================================
# Lazy singleton
# ============================================================================

_store = None
_manager = None


def _ensure_manager() -> VersionManager:
    global _store, _manager
    if _manager is None:
        _store = VersionStore()
        _manager = VersionManager(_store)
    return _manager


# ============================================================================
# version_manage action handlers
# ============================================================================

def _vm_create(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    if not object_type or not object_id:
        return {"error": "object_type and object_id are required for 'create' action"}
    try:
        obj_type = VersionedObjectType(object_type)
    except ValueError:
        return {"error": f"Invalid object_type. Valid: {[e.value for e in VersionedObjectType]}"}
    manager = _ensure_manager()
    snapshot = {"id": object_id, "type": object_type}
    tags_str = kwargs.get("tags")
    tag_list = [t.strip() for t in tags_str.split(",") if t.strip()] if tags_str else []
    version = manager.snapshot(
        obj_type, object_id, snapshot, ChangeType.UPDATE,
        kwargs.get("description"), kwargs.get("user"),
        kwargs.get("bump", "patch"), tag_list,
    )
    return version.model_dump(mode="json")


def _vm_create_hierarchy(**kwargs) -> Dict[str, Any]:
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'create_hierarchy' action"}

    try:
        from src.hierarchy.service import HierarchyService

        hierarchy_service = HierarchyService(kwargs.get("data_dir", "data"))
        project = hierarchy_service.get_project(project_id)
        if not project:
            return {"error": f"Hierarchy project '{project_id}' not found"}
        hierarchies = hierarchy_service.list_hierarchies(project_id)
    except Exception as e:
        return {"error": f"Failed to load hierarchy snapshot for project '{project_id}': {e}"}

    mappings: List[Dict[str, Any]] = []
    for hierarchy in hierarchies:
        for mapping in hierarchy.get("mapping", []) or []:
            if isinstance(mapping, dict):
                mappings.append(mapping)

    manager = _ensure_manager()
    snapshot = {
        "project_id": project_id,
        "project": project,
        "hierarchies": hierarchies,
        "hierarchy_count": len(hierarchies),
        "mappings": mappings,
        "mapping_count": len(mappings),
        "captured_at": datetime.now(timezone.utc).isoformat(),
    }
    version = manager.snapshot(
        VersionedObjectType.HIERARCHY_PROJECT, project_id, snapshot,
        ChangeType.UPDATE, kwargs.get("description"), kwargs.get("user"),
        kwargs.get("bump", "patch"),
    )
    return version.model_dump(mode="json")


def _vm_create_catalog_asset(**kwargs) -> Dict[str, Any]:
    asset_id = kwargs.get("asset_id")
    if not asset_id:
        return {"error": "asset_id is required for 'create_catalog_asset' action"}
    manager = _ensure_manager()
    snapshot = {"asset_id": asset_id}
    version = manager.snapshot(
        VersionedObjectType.CATALOG_ASSET, asset_id, snapshot,
        ChangeType.UPDATE, kwargs.get("description"), kwargs.get("user"),
        kwargs.get("bump", "patch"),
    )
    return version.model_dump(mode="json")


def _vm_get(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    if not object_type or not object_id:
        return {"error": "object_type and object_id are required for 'get' action"}
    try:
        obj_type = VersionedObjectType(object_type)
    except ValueError:
        return {"error": "Invalid object_type"}
    manager = _ensure_manager()
    v = manager.store.get_version(obj_type, object_id, kwargs.get("version"))
    return v.model_dump(mode="json") if v else {"error": "Version not found"}


def _vm_list(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    if not object_type or not object_id:
        return {"error": "object_type and object_id are required for 'list' action"}
    try:
        obj_type = VersionedObjectType(object_type)
    except ValueError:
        return {"error": "Invalid object_type"}
    manager = _ensure_manager()
    limit = kwargs.get("limit", 20)
    versions = manager.get_history(obj_type, object_id, limit)
    return {
        "object_type": object_type,
        "object_id": object_id,
        "count": len(versions),
        "versions": [v.model_dump(mode="json") for v in versions],
    }


def _vm_stats(**kwargs) -> Dict[str, Any]:
    manager = _ensure_manager()
    return manager.get_stats()


# ============================================================================
# version_diff action handlers
# ============================================================================

def _vd_diff(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    from_version = kwargs.get("from_version")
    if not object_type or not object_id or not from_version:
        return {"error": "object_type, object_id, and from_version are required for 'diff' action"}
    try:
        obj_type = VersionedObjectType(object_type)
        manager = _ensure_manager()
        diff = manager.diff(obj_type, object_id, from_version, kwargs.get("to_version"))
        return diff.model_dump()
    except ValueError as e:
        return {"error": str(e)}


def _vd_diff_latest(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    if not object_type or not object_id:
        return {"error": "object_type and object_id are required for 'diff_latest' action"}
    try:
        obj_type = VersionedObjectType(object_type)
    except ValueError:
        return {"error": "Invalid object_type"}
    manager = _ensure_manager()
    versions = manager.get_history(obj_type, object_id, 2)
    if len(versions) < 2:
        return {"error": "Need at least 2 versions to compare"}
    diff = manager.diff(obj_type, object_id, versions[1].version, versions[0].version)
    return diff.model_dump()


def _vd_preview_rollback(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    to_version = kwargs.get("to_version")
    if not object_type or not object_id or not to_version:
        return {"error": "object_type, object_id, and to_version are required for 'preview_rollback' action"}
    try:
        obj_type = VersionedObjectType(object_type)
        manager = _ensure_manager()
        return manager.preview_rollback(obj_type, object_id, to_version)
    except ValueError as e:
        return {"error": str(e)}


def _vd_rollback(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    to_version = kwargs.get("to_version")
    if not object_type or not object_id or not to_version:
        return {"error": "object_type, object_id, and to_version are required for 'rollback' action"}
    try:
        obj_type = VersionedObjectType(object_type)
        manager = _ensure_manager()
        snapshot = manager.rollback(obj_type, object_id, to_version, kwargs.get("user"))
        return {"success": True, "rolled_back_to": to_version, "snapshot": snapshot}
    except ValueError as e:
        return {"error": str(e)}


# ============================================================================
# version_meta action handlers
# ============================================================================

def _vmt_tag(**kwargs) -> Dict[str, Any]:
    object_type = kwargs.get("object_type")
    object_id = kwargs.get("object_id")
    version = kwargs.get("version")
    tag = kwargs.get("tag")
    if not object_type or not object_id or not version or not tag:
        return {"error": "object_type, object_id, version, and tag are required for 'tag' action"}
    try:
        obj_type = VersionedObjectType(object_type)
        manager = _ensure_manager()
        remove = kwargs.get("remove", False)
        success = manager.tag_version(obj_type, object_id, version, tag, remove)
        action_str = "removed" if remove else "added"
        return {"success": success, "message": f"Tag {action_str}: {tag}"}
    except Exception as e:
        return {"error": str(e)}


def _vmt_search(**kwargs) -> Dict[str, Any]:
    object_type_str = kwargs.get("object_type")
    manager = _ensure_manager()
    query = VersionQuery(
        object_type=VersionedObjectType(object_type_str) if object_type_str else None,
        changed_by=kwargs.get("changed_by"),
        tag=kwargs.get("tag"),
        limit=kwargs.get("limit", 50),
    )
    versions = manager.search(query)
    return {
        "count": len(versions),
        "versions": [v.model_dump(mode="json") for v in versions],
    }


# ============================================================================
# Action dispatch dictionaries
# ============================================================================

_MANAGE_ACTIONS = {
    "create": _vm_create,
    "create_hierarchy": _vm_create_hierarchy,
    "create_catalog_asset": _vm_create_catalog_asset,
    "get": _vm_get,
    "list": _vm_list,
    "stats": _vm_stats,
}

_DIFF_ACTIONS = {
    "diff": _vd_diff,
    "diff_latest": _vd_diff_latest,
    "preview_rollback": _vd_preview_rollback,
    "rollback": _vd_rollback,
}

_META_ACTIONS = {
    "tag": _vmt_tag,
    "search": _vmt_search,
}


# ============================================================================
# Unified dispatch functions
# ============================================================================

def dispatch_version_manage(action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a version_manage action."""
    handler = _MANAGE_ACTIONS.get(action)
    if not handler:
        return {
            "error": f"Unknown action: '{action}'",
            "valid_actions": sorted(_MANAGE_ACTIONS.keys()),
        }
    try:
        return handler(**kwargs)
    except Exception as e:
        logger.error(f"version_manage({action}) failed: {e}")
        return {"error": f"version_manage({action}) failed: {e}"}


def dispatch_version_diff(action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a version_diff action."""
    handler = _DIFF_ACTIONS.get(action)
    if not handler:
        return {
            "error": f"Unknown action: '{action}'",
            "valid_actions": sorted(_DIFF_ACTIONS.keys()),
        }
    try:
        return handler(**kwargs)
    except Exception as e:
        logger.error(f"version_diff({action}) failed: {e}")
        return {"error": f"version_diff({action}) failed: {e}"}


def dispatch_version_meta(action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a version_meta action."""
    handler = _META_ACTIONS.get(action)
    if not handler:
        return {
            "error": f"Unknown action: '{action}'",
            "valid_actions": sorted(_META_ACTIONS.keys()),
        }
    try:
        return handler(**kwargs)
    except Exception as e:
        logger.error(f"version_meta({action}) failed: {e}")
        return {"error": f"version_meta({action}) failed: {e}"}


# ============================================================================
# Registration
# ============================================================================

def register_unified_versioning_tools(mcp):
    """Register the 3 unified versioning MCP tools. Returns manager instance."""

    @mcp.tool()
    def version_manage(
        action: str,
        object_type: Optional[str] = None,
        object_id: Optional[str] = None,
        description: Optional[str] = None,
        bump: str = "patch",
        user: Optional[str] = None,
        tags: Optional[str] = None,
        project_id: Optional[str] = None,
        asset_id: Optional[str] = None,
        version: Optional[str] = None,
        limit: int = 20,
    ) -> Dict[str, Any]:
        """
        Unified version management tool. Replaces 6 individual tools.

        Actions:
        - create: Create a versioned snapshot (requires object_type, object_id)
        - create_hierarchy: Version a hierarchy project (requires project_id)
        - create_catalog_asset: Version a catalog asset (requires asset_id)
        - get: Get a specific version or latest (requires object_type, object_id)
        - list: List version history (requires object_type, object_id)
        - stats: Get versioning statistics

        Returns:
            Action-specific result dict
        """
        return dispatch_version_manage(action, **{
            k: v for k, v in {
                "object_type": object_type, "object_id": object_id,
                "description": description, "bump": bump, "user": user,
                "tags": tags, "project_id": project_id, "asset_id": asset_id,
                "version": version, "limit": limit,
            }.items() if v is not None
        })

    @mcp.tool()
    def version_diff(
        action: str,
        object_type: Optional[str] = None,
        object_id: Optional[str] = None,
        from_version: Optional[str] = None,
        to_version: Optional[str] = None,
        user: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Unified version diff and rollback tool. Replaces 4 individual tools.

        Actions:
        - diff: Compare two versions (requires object_type, object_id, from_version)
        - diff_latest: Compare current to previous version (requires object_type, object_id)
        - preview_rollback: Preview rollback result (requires object_type, object_id, to_version)
        - rollback: Rollback to a previous version (requires object_type, object_id, to_version)

        Returns:
            Action-specific result dict
        """
        return dispatch_version_diff(action, **{
            k: v for k, v in {
                "object_type": object_type, "object_id": object_id,
                "from_version": from_version, "to_version": to_version,
                "user": user,
            }.items() if v is not None
        })

    @mcp.tool()
    def version_meta(
        action: str,
        object_type: Optional[str] = None,
        object_id: Optional[str] = None,
        version: Optional[str] = None,
        tag: Optional[str] = None,
        remove: bool = False,
        changed_by: Optional[str] = None,
        limit: int = 50,
    ) -> Dict[str, Any]:
        """
        Unified version metadata tool. Replaces 2 individual tools.

        Actions:
        - tag: Add or remove a tag (requires object_type, object_id, version, tag)
        - search: Search versions across objects (optional object_type, changed_by, tag filters)

        Returns:
            Action-specific result dict
        """
        return dispatch_version_meta(action, **{
            k: v for k, v in {
                "object_type": object_type, "object_id": object_id,
                "version": version, "tag": tag, "remove": remove,
                "changed_by": changed_by, "limit": limit,
            }.items() if v is not None
        })

    logger.info("Registered 3 unified versioning tools: version_manage, version_diff, version_meta")
    return _ensure_manager()
