"""Integration between ServerConfig and ClientHandler.

This module provides high-level functions that bridge server configurations
and client handlers for setting up MCP servers.
"""

import importlib.util
import shutil
import sys
from pathlib import Path
from typing import Any

from .clients import ClientHandler, VSCodeHandler
from .servers import ServerConfig
from .utils import (
    normalize_path_parameter,
    validate_parameter_value,
    validate_required_parameters,
)


def _detect_mcp_config_directory(venv_path: str | None = None) -> Path:
    """Detect the mcp-config installation directory.

    Args:
        venv_path: Optional path to virtual environment

    Returns:
        Path to the mcp-config directory
    """
    # Method 1: If venv_path is provided, use its parent as the mcp-config directory
    if venv_path:
        venv_path_obj = Path(venv_path)
        if venv_path_obj.exists():
            return venv_path_obj.parent

    # Method 2: Use the directory where this module is installed
    # Go up from src/mcp_config/integration.py to project root
    current_file = Path(__file__)
    mcp_config_dir = (
        current_file.parent.parent.parent
    )  # Go up: integration.py -> mcp_config -> src -> project root

    return mcp_config_dir


def is_command_available(command: str) -> bool:
    """Check if a command is available in the system PATH.

    Args:
        command: Command name to check

    Returns:
        True if command is available in PATH
    """
    return shutil.which(command) is not None


def is_package_installed(package_name: str) -> bool:
    """Check if a package is installed.

    Args:
        package_name: Name of the package to check

    Returns:
        True if package is installed and importable
    """
    try:
        spec = importlib.util.find_spec(package_name)
        return spec is not None
    except (ImportError, ModuleNotFoundError):
        return False


def get_mcp_code_checker_command_mode() -> str:
    """Determine the best command mode for MCP Code Checker.

    Returns:
        One of: 'cli_command', 'python_module', 'development', 'not_available'
    """
    # First check for CLI command
    if is_command_available("mcp-code-checker"):
        return "cli_command"

    # Check if package is installed
    if is_package_installed("mcp_code_checker"):
        return "python_module"

    # Check if we're in development mode (src/main.py exists)
    if Path("src/main.py").exists():
        return "development"

    return "not_available"


def get_mcp_filesystem_server_command_mode() -> str:
    """Determine the best command mode for MCP Filesystem Server.

    Returns:
        One of: 'cli_command', 'python_module', 'development', 'not_available'
    """
    # First check for CLI command
    if is_command_available("mcp-server-filesystem"):
        return "cli_command"

    # Check if package is installed
    if is_package_installed("mcp_server_filesystem"):
        return "python_module"

    # Check for development mode (if we're in a filesystem server project)
    dev_indicators = [
        "setup.py",
        "pyproject.toml",
        "src/mcp_server_filesystem",
        "mcp_server_filesystem/__init__.py",
    ]
    if any(Path(indicator).exists() for indicator in dev_indicators):
        # Additional check: look for filesystem server specific files
        if (
            Path("src").exists()
            and any(Path("src").glob("*filesystem*"))
            or Path("mcp_server_filesystem").exists()
        ):
            return "development"

    return "not_available"


def get_server_command_mode(server_type: str) -> str:
    """Unified function to determine the best command mode for any supported server.

    Args:
        server_type: Type of server (e.g., 'mcp-code-checker', 'mcp-server-filesystem')

    Returns:
        One of: 'cli_command', 'python_module', 'development', 'not_available'
    """
    if server_type == "mcp-code-checker":
        return get_mcp_code_checker_command_mode()
    elif server_type == "mcp-server-filesystem":
        return get_mcp_filesystem_server_command_mode()
    else:
        # For unknown server types, just check if CLI command exists
        cli_command = server_type.replace("_", "-")
        if is_command_available(cli_command):
            return "cli_command"
        return "not_available"


def _get_server_module_name(server_name: str) -> str:
    """Get the Python module name for a server.

    Args:
        server_name: Name of the server (e.g., "mcp-code-checker")

    Returns:
        Python module name for -m execution
    """
    module_mapping = {
        "mcp-code-checker": "mcp_code_checker",
        "mcp-server-filesystem": "mcp_server_filesystem",
    }
    return module_mapping.get(server_name, server_name.replace("-", "_"))


def _build_server_config_with_cli_detection(
    server_config: ServerConfig,
    normalized_params: dict[str, Any],
    effective_python: str,
    module_name: str,
) -> dict[str, Any]:
    """Build server configuration with CLI command detection and fallback.

    This function handles the common pattern for servers that support both CLI
    commands and Python module execution modes.

    Args:
        server_config: Server configuration definition
        normalized_params: Normalized parameter dictionary
        effective_python: Python executable to use
        module_name: Python module name for -m execution (e.g., "mcp_code_checker")

    Returns:
        Configuration dictionary with command and args
    """
    cli_command_name = server_config.get_cli_command_name()
    if not cli_command_name:
        # Server doesn't support CLI commands, use Python module mode
        args = server_config.generate_args(normalized_params, use_cli_command=True)
        return {
            "command": effective_python,
            "args": ["-m", module_name] + args,
        }

    # Check for CLI command availability first
    command_mode = get_server_command_mode(server_config.name)

    if command_mode == "cli_command":
        # Try to use CLI command when available
        cli_command = _find_cli_executable(
            cli_command_name, normalized_params.get("venv_path")
        )
        if cli_command:
            # For filesystem server, don't include python-executable
            # For code checker, ensure python_executable is included
            if server_config.name == "mcp-code-checker":
                if (
                    "python_executable" not in normalized_params
                    or not normalized_params["python_executable"]
                ):
                    normalized_params["python_executable"] = effective_python
            elif server_config.name == "mcp-server-filesystem":
                # Remove python_executable and venv_path for filesystem server if present
                # The filesystem server doesn't support these parameters in CLI mode
                normalized_params.pop("python_executable", None)
                normalized_params.pop("venv_path", None)

            args = server_config.generate_args(normalized_params, use_cli_command=True)
            return {
                "command": cli_command,
                "args": args,
            }
        else:
            # CLI command not found, fallback to Python module mode
            command_mode = "python_module"

    # Use Python module mode (fallback or when CLI not available)
    # For code checker, ensure python_executable is included
    # For filesystem server, don't include it
    if server_config.name == "mcp-code-checker":
        if (
            "python_executable" not in normalized_params
            or not normalized_params["python_executable"]
        ):
            normalized_params["python_executable"] = effective_python
    elif server_config.name == "mcp-server-filesystem":
        # Remove python_executable and venv_path for filesystem server
        # The filesystem server doesn't support these parameters
        normalized_params.pop("python_executable", None)
        normalized_params.pop("venv_path", None)

    # Generate args without the main script path (CLI mode)
    args = server_config.generate_args(normalized_params, use_cli_command=True)

    # Use effective Python executable as command with -m module_name
    return {
        "command": effective_python,
        "args": ["-m", module_name] + args,
    }


def _find_cli_executable(command_name: str, venv_path: str | None = None) -> str | None:
    """Find the CLI executable, checking venv first if provided.

    Args:
        command_name: Name of the command to find (e.g., "mcp-code-checker")
        venv_path: Optional path to virtual environment to check first

    Returns:
        Full path to executable if found, None otherwise
    """
    # Check venv first if provided
    if venv_path:
        venv_path_obj = Path(venv_path)
        if venv_path_obj.exists():
            if sys.platform == "win32":
                venv_exe = venv_path_obj / "Scripts" / f"{command_name}.exe"
            else:
                venv_exe = venv_path_obj / "bin" / command_name

            if venv_exe.exists():
                return str(venv_exe)

    # Fall back to system PATH
    system_exe = shutil.which(command_name)
    if system_exe:
        # Normalize the path and ensure lowercase .exe extension on Windows
        system_path = Path(system_exe)
        if sys.platform == "win32" and system_path.suffix.upper() == ".EXE":
            # Replace with lowercase .exe
            system_path = system_path.with_suffix(".exe")
        return str(system_path)

    return None


def generate_vscode_command(
    server_type: str, server_config: dict[str, Any], workspace: bool = True
) -> dict[str, Any]:
    """Generate VSCode-compatible server configuration.

    Args:
        server_type: Type of server (e.g., "mcp-code-checker")
        server_config: Raw server configuration
        workspace: Whether this is for workspace config

    Returns:
        VSCode-formatted server configuration
    """
    config: dict[str, Any] = {}

    # For mcp-code-checker and mcp-server-filesystem, use the reliable execution model
    # The input server_config should already be in the correct format from generate_client_config
    if server_type in ["mcp-code-checker", "mcp-server-filesystem"]:
        # Just use the config as-is since generate_client_config already produces the right format
        config["command"] = server_config.get("command", sys.executable)
        config["args"] = server_config.get("args", [])
    else:
        # Other server types - use as-is
        config["command"] = server_config.get("command")
        config["args"] = server_config.get("args", [])

    # Handle environment variables
    if "env" in server_config and server_config["env"]:
        config["env"] = server_config["env"]

    # Preserve metadata field for internal use
    if "_server_type" in server_config:
        config["_server_type"] = server_config["_server_type"]

    # Always use absolute paths for consistency with Claude Desktop
    config = make_paths_absolute(config)

    return config


def make_paths_relative(config: dict[str, Any], base_path: Path) -> dict[str, Any]:
    """Convert absolute paths to relative where possible.

    Args:
        config: Server configuration
        base_path: Base path to make paths relative to

    Returns:
        Configuration with relative paths
    """
    updated = config.copy()

    # Process arguments
    if "args" in updated:
        updated_args = []
        skip_next = False

        for i, arg in enumerate(updated["args"]):
            if skip_next:
                skip_next = False
                # This is the value for a path argument
                try:
                    path_obj = Path(arg)
                    if path_obj.is_absolute():
                        try:
                            rel_path = path_obj.relative_to(base_path)
                            # Only use relative path if it doesn't go up directories
                            if not str(rel_path).startswith(".."):
                                updated_args.append(str(rel_path).replace("\\", "/"))
                            else:
                                updated_args.append(arg)
                        except ValueError:
                            # Path is not relative to base, keep absolute
                            updated_args.append(arg)
                    else:
                        updated_args.append(arg)
                except (ValueError, OSError):
                    updated_args.append(arg)
            elif arg in [
                "--project-dir",
                "--python-executable",
                "--venv-path",
                "--log-file",
            ]:
                skip_next = True
                updated_args.append(arg)
            else:
                updated_args.append(arg)

        updated["args"] = updated_args

    return updated


def make_paths_absolute(config: dict[str, Any]) -> dict[str, Any]:
    """Ensure all paths are absolute.

    Args:
        config: Server configuration

    Returns:
        Configuration with absolute paths
    """
    updated = config.copy()

    # Process arguments
    if "args" in updated:
        updated_args = []
        skip_next = False

        for i, arg in enumerate(updated["args"]):
            if skip_next:
                skip_next = False
                # This is the value for a path argument
                try:
                    path_obj = Path(arg)
                    if not path_obj.is_absolute():
                        updated_args.append(str(path_obj.resolve()))
                    else:
                        updated_args.append(arg)
                except (ValueError, OSError):
                    updated_args.append(arg)
            elif arg in [
                "--project-dir",
                "--python-executable",
                "--venv-path",
                "--log-file",
            ]:
                skip_next = True
                updated_args.append(arg)
            else:
                updated_args.append(arg)

        updated["args"] = updated_args

    return updated


def build_server_config(
    server_config: ServerConfig,
    user_params: dict[str, Any],
    python_executable: str | None = None,
) -> dict[str, Any]:
    """Build server configuration for preview/dry-run.

    This is a simplified version of generate_client_config for preview purposes.

    Args:
        server_config: Server configuration definition
        user_params: User-provided parameter values
        python_executable: Python executable to use

    Returns:
        Configuration dictionary for preview
    """
    # Get project directory
    project_dir = Path(user_params.get("project_dir", ".")).resolve()

    # Normalize parameters
    normalized_params = {}
    for key, value in user_params.items():
        if key in ["project_dir", "test_folder", "log_file", "venv_path"] and value:
            normalized_params[key] = str(
                Path(value).resolve()
                if Path(value).is_absolute()
                else project_dir / value
            )
        else:
            normalized_params[key] = value

    # Use venv Python if available
    effective_python = python_executable or sys.executable
    if "venv_path" in normalized_params and normalized_params["venv_path"]:
        venv_path = Path(normalized_params["venv_path"])
        if venv_path.exists():
            if sys.platform == "win32":
                venv_python = venv_path / "Scripts" / "python.exe"
            else:
                venv_python = venv_path / "bin" / "python"
            if venv_python.exists():
                effective_python = str(venv_python)

    # Generate configuration based on server type and available command mode
    if server_config.name in ["mcp-code-checker", "mcp-server-filesystem"]:
        # Use unified CLI detection for supported servers
        module_name = _get_server_module_name(server_config.name)
        config = _build_server_config_with_cli_detection(
            server_config, normalized_params, effective_python, module_name
        )
    else:
        # Other servers - use standard approach with effective Python
        args = server_config.generate_args(normalized_params)
        config = {
            "command": effective_python,
            "args": args,
        }

    # Add environment if needed
    # Use mcp-config directory for PYTHONPATH, not project directory
    # Capture venv_path before server config might modify it
    original_venv_path = normalized_params.get("venv_path")
    mcp_config_dir = _detect_mcp_config_directory(original_venv_path)

    pythonpath = str(mcp_config_dir)
    # Ensure trailing separator on Windows
    if sys.platform == "win32" and not pythonpath.endswith("\\"):
        pythonpath += "\\"
    config["env"] = {"PYTHONPATH": pythonpath}

    return config


def generate_client_config(
    server_config: ServerConfig,
    _server_name: str,
    user_params: dict[str, Any],
    python_executable: str | None = None,
    client_handler: ClientHandler | None = None,
) -> dict[str, Any]:
    """Generate client configuration from server config and user parameters.

    Args:
        server_config: Server configuration definition
        _server_name: User-provided server instance name (for future use)
        user_params: User-provided parameter values (with underscores)
        python_executable: Path to Python executable to use (auto-detect if None)
        client_handler: Client handler instance for client-specific formatting

    Returns:
        Client configuration dictionary ready for JSON serialization

    Raises:
        ValueError: If required parameters are missing or invalid
    """
    # Validate required parameters
    # Note: user_params already has underscore format from argparse
    errors = validate_required_parameters(server_config, user_params)
    if errors:
        raise ValueError(f"Parameter validation failed: {', '.join(errors)}")

    # Validate individual parameter values
    for param in server_config.parameters:
        # Convert parameter name to underscore format to match user_params
        param_key = param.name.replace("-", "_")
        if param_key in user_params:
            value = user_params[param_key]
            param_errors = validate_parameter_value(param, value)
            if param_errors:
                errors.extend(param_errors)

    if errors:
        raise ValueError(f"Parameter validation failed: {', '.join(errors)}")

    # Get project directory (required for path normalization)
    project_dir = Path(user_params.get("project_dir", ".")).resolve()

    # Normalize path parameters (convert back to underscore format for generate_args)
    normalized_params = {}
    for param in server_config.parameters:
        param_key = param.name.replace("-", "_")
        if param_key in user_params:
            value = user_params[param_key]
            if param.param_type == "path" and value is not None:
                # Normalize path relative to project directory
                normalized_params[param_key] = normalize_path_parameter(
                    value, project_dir
                )
            else:
                normalized_params[param_key] = value

    # Use provided Python executable or default to current
    if python_executable is None:
        python_executable = sys.executable

    # Determine effective Python executable (prioritize venv if available)
    effective_python = python_executable
    if "venv_path" in normalized_params and normalized_params["venv_path"]:
        venv_path = Path(normalized_params["venv_path"])
        if venv_path.exists():
            if sys.platform == "win32":
                venv_python = venv_path / "Scripts" / "python.exe"
            else:
                venv_python = venv_path / "bin" / "python"
            if venv_python.exists():
                effective_python = str(venv_python)

    # Generate configuration based on server type
    if server_config.name in ["mcp-code-checker", "mcp-server-filesystem"]:
        # Use unified CLI detection for supported servers
        module_name = _get_server_module_name(server_config.name)
        client_config = _build_server_config_with_cli_detection(
            server_config, normalized_params, effective_python, module_name
        )
    else:
        # Other servers - use standard approach with effective Python
        args = server_config.generate_args(normalized_params)
        client_config = {
            "command": effective_python,
            "args": args,
        }

    # Store metadata separately (will be handled by ClientHandler)
    # The _server_type will be passed through setup_server and extracted there
    client_config["_server_type"] = server_config.name

    # Add environment variables if needed
    env = {}

    # Add PYTHONPATH to include the mcp-config directory (where this tool is installed)
    # This ensures the virtual environment and dependencies are accessible
    # We need to capture venv_path BEFORE it might be stripped out by server config
    original_venv_path = normalized_params.get("venv_path")
    mcp_config_dir = _detect_mcp_config_directory(original_venv_path)

    pythonpath = str(mcp_config_dir)
    # Ensure trailing separator on Windows
    if sys.platform == "win32" and not pythonpath.endswith("\\"):
        pythonpath += "\\"
    env["PYTHONPATH"] = pythonpath

    # Venv Python is already handled above in effective_python calculation

    if env:
        client_config["env"] = env

    # Apply VSCode-specific formatting if needed
    if client_handler and isinstance(client_handler, VSCodeHandler):
        workspace = getattr(client_handler, "workspace", True)
        client_config = generate_vscode_command(
            server_config.name, client_config, workspace
        )

    return client_config


def setup_mcp_server(
    client_handler: ClientHandler,
    server_config: ServerConfig,
    server_name: str,
    user_params: dict[str, Any],
    python_executable: str | None = None,
    dry_run: bool = False,
) -> dict[str, Any]:
    """High-level function to set up an MCP server in a client.

    Args:
        client_handler: Client handler instance
        server_config: Server configuration definition
        server_name: User-provided server instance name
        user_params: User-provided parameter values
        python_executable: Python executable to use (auto-detect if None)
        dry_run: If True, return what would be done without applying changes

    Returns:
        Dictionary with operation results and details
    """
    result = {
        "success": False,
        "server_name": server_name,
        "operation": "setup",
        "dry_run": dry_run,
    }

    try:
        # Generate the client configuration
        client_config = generate_client_config(
            server_config, server_name, user_params, python_executable, client_handler
        )

        result["config"] = client_config
        result["config_path"] = str(client_handler.get_config_path())

        if dry_run:
            # Just return what would be done
            result["success"] = True
            result["message"] = f"Would set up server '{server_name}'"
            return result

        # Actually set up the server
        success = client_handler.setup_server(server_name, client_config)

        if success:
            result["success"] = True
            result["message"] = f"Successfully set up server '{server_name}'"

            # Get backup path if available
            try:
                # Try to get the most recent backup
                config_path = client_handler.get_config_path()
                backup_files = list(
                    config_path.parent.glob("claude_desktop_config_backup_*.json")
                )
                if backup_files:
                    # Sort by modification time and get the most recent
                    latest_backup = max(backup_files, key=lambda p: p.stat().st_mtime)
                    result["backup_path"] = str(latest_backup)
            except Exception:
                pass  # Backup path is optional

        else:
            result["message"] = f"Failed to set up server '{server_name}'"

    except Exception as e:
        result["error"] = str(e)
        result["message"] = f"Error setting up server: {e}"

    return result


def remove_mcp_server(
    client_handler: ClientHandler,
    server_name: str,
    dry_run: bool = False,
) -> dict[str, Any]:
    """Remove an MCP server from client configuration.

    Args:
        client_handler: Client handler instance
        server_name: Name of server to remove
        dry_run: If True, return what would be done without applying changes

    Returns:
        Dictionary with operation results
    """
    result = {
        "success": False,
        "server_name": server_name,
        "operation": "remove",
        "dry_run": dry_run,
    }

    try:
        # Check if server exists and is managed
        all_servers = client_handler.list_all_servers()
        server_info = None
        for server in all_servers:
            if server["name"] == server_name:
                server_info = server
                break

        if not server_info:
            result["message"] = f"Server '{server_name}' not found"
            return result

        if not server_info.get("managed", False):
            result["message"] = (
                f"Server '{server_name}' is not managed by this tool. "
                "Cannot remove external servers."
            )
            return result

        result["config_path"] = str(client_handler.get_config_path())

        if dry_run:
            result["success"] = True
            result["message"] = f"Would remove server '{server_name}'"
            return result

        # Actually remove the server
        success = client_handler.remove_server(server_name)

        if success:
            result["success"] = True
            result["message"] = f"Successfully removed server '{server_name}'"

            # Get backup path if available
            try:
                config_path = client_handler.get_config_path()
                backup_files = list(
                    config_path.parent.glob("claude_desktop_config_backup_*.json")
                )
                if backup_files:
                    latest_backup = max(backup_files, key=lambda p: p.stat().st_mtime)
                    result["backup_path"] = str(latest_backup)
            except Exception:
                pass

        else:
            result["message"] = f"Failed to remove server '{server_name}'"

    except Exception as e:
        result["error"] = str(e)
        result["message"] = f"Error removing server: {e}"

    return result
