"""
SuperClaude Installation Operation Module
Refactored from install.py for unified CLI hub
"""

import sys
import time
from pathlib import Path
from ...utils.paths import get_home_directory
from typing import List, Optional, Dict, Any
import argparse

from ...core.installer import Installer
from ...core.registry import ComponentRegistry
from ...services.config import ConfigService
from ...core.validator import Validator
from ...utils.ui import (
    display_header,
    display_info,
    display_success,
    display_error,
    display_warning,
    Menu,
    confirm,
    ProgressBar,
    Colors,
    format_size,
    prompt_api_key,
)
from ...utils.environment import setup_environment_variables
from ...utils.logger import get_logger
from ... import DEFAULT_INSTALL_DIR, PROJECT_ROOT, DATA_DIR
from . import OperationBase


class InstallOperation(OperationBase):
    """Installation operation implementation"""

    def __init__(self):
        super().__init__("install")


def register_parser(subparsers, global_parser=None) -> argparse.ArgumentParser:
    """Register installation CLI arguments"""
    parents = [global_parser] if global_parser else []

    parser = subparsers.add_parser(
        "install",
        help="Install SuperClaude framework components",
        description="Install SuperClaude Framework with various options and profiles",
        epilog="""
Examples:
  SuperClaude install                          # Interactive installation
  SuperClaude install --dry-run                # Dry-run mode  
  SuperClaude install --components core mcp    # Specific components
  SuperClaude install --verbose --force        # Verbose with force mode
        """,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        parents=parents,
    )

    # Installation mode options

    parser.add_argument(
        "--components", type=str, nargs="+", help="Specific components to install"
    )

    # Installation options
    parser.add_argument("--no-backup", action="store_true", help="Skip backup creation")

    parser.add_argument(
        "--list-components",
        action="store_true",
        help="List available components and exit",
    )

    parser.add_argument(
        "--diagnose",
        action="store_true",
        help="Run system diagnostics and show installation help",
    )

    return parser


def validate_system_requirements(
    validator: Validator, component_names: List[str]
) -> bool:
    """Validate system requirements"""
    logger = get_logger()

    logger.info("Validating system requirements...")

    try:
        # Load requirements configuration
        config_manager = ConfigService(DATA_DIR)
        requirements = config_manager.get_requirements_for_components(component_names)

        # Validate requirements
        success, errors = validator.validate_component_requirements(
            component_names, requirements
        )

        if success:
            logger.success("All system requirements met")
            return True
        else:
            logger.error("System requirements not met:")
            for error in errors:
                logger.error(f"  - {error}")

            # Provide additional guidance
            print(f"\n{Colors.CYAN}💡 Installation Help:{Colors.RESET}")
            print(
                "  Run 'superclaude install --diagnose' for detailed system diagnostics"
            )
            print("  and step-by-step installation instructions.")

            return False

    except Exception as e:
        logger.error(f"Could not validate system requirements: {e}")
        return False


def get_components_to_install(
    args: argparse.Namespace, registry: ComponentRegistry, config_manager: ConfigService
) -> Optional[List[str]]:
    """Determine which components to install"""
    logger = get_logger()

    # Explicit components specified
    if args.components:
        if "all" in args.components:
            components = ["core", "commands", "agents", "modes", "mcp", "mcp_docs"]
        else:
            components = args.components

        # If mcp or mcp_docs is specified non-interactively, we should still ask which servers to install.
        if "mcp" in components or "mcp_docs" in components:
            selected_servers = select_mcp_servers(registry)
            if not hasattr(config_manager, "_installation_context"):
                config_manager._installation_context = {}
            config_manager._installation_context["selected_mcp_servers"] = (
                selected_servers
            )

            # If the user selected some servers, ensure both mcp and mcp_docs are included
            if selected_servers:
                if "mcp" not in components:
                    components.append("mcp")
                    logger.debug(
                        f"Auto-added 'mcp' component for selected servers: {selected_servers}"
                    )
                if "mcp_docs" not in components:
                    components.append("mcp_docs")
                    logger.debug(
                        f"Auto-added 'mcp_docs' component for selected servers: {selected_servers}"
                    )

                logger.info(f"Final components to install: {components}")

            # If mcp_docs was explicitly requested but no servers selected, allow auto-detection
            elif not selected_servers and "mcp_docs" in components:
                logger.info("mcp_docs component will auto-detect existing MCP servers")
                logger.info("Documentation will be installed for any detected servers")

        return components

    # Interactive two-stage selection
    return interactive_component_selection(registry, config_manager)


def collect_api_keys_for_servers(
    selected_servers: List[str], mcp_instance
) -> Dict[str, str]:
    """
    Collect API keys for servers that require them

    Args:
        selected_servers: List of selected server keys
        mcp_instance: MCP component instance

    Returns:
        Dictionary of environment variable names to API key values
    """
    # Filter servers needing keys
    servers_needing_keys = [
        (server_key, mcp_instance.mcp_servers[server_key])
        for server_key in selected_servers
        if server_key in mcp_instance.mcp_servers
        and mcp_instance.mcp_servers[server_key].get("requires_api_key", False)
    ]

    if not servers_needing_keys:
        return {}

    # Display API key configuration header
    print(f"\n{Colors.CYAN}{Colors.BRIGHT}=== API Key Configuration ==={Colors.RESET}")
    print(
        f"{Colors.YELLOW}The following servers require API keys for full functionality:{Colors.RESET}\n"
    )

    collected_keys = {}
    for server_key, server_info in servers_needing_keys:
        api_key_env = server_info.get("api_key_env")
        service_name = server_info["name"]

        if api_key_env:
            key = prompt_api_key(service_name, api_key_env)
            if key:
                collected_keys[api_key_env] = key

    return collected_keys


def select_mcp_servers(registry: ComponentRegistry) -> List[str]:
    """Stage 1: MCP Server Selection with API Key Collection"""
    logger = get_logger()

    try:
        # Get MCP component to access server list
        mcp_instance = registry.get_component_instance(
            "mcp", get_home_directory() / ".claude"
        )
        if not mcp_instance or not hasattr(mcp_instance, "mcp_servers"):
            logger.error("Could not access MCP server information")
            return []

        # Create MCP server menu
        mcp_servers = mcp_instance.mcp_servers
        server_options = []

        for server_key, server_info in mcp_servers.items():
            description = server_info["description"]
            api_key_note = (
                " (requires API key)"
                if server_info.get("requires_api_key", False)
                else ""
            )
            server_options.append(f"{server_key} - {description}{api_key_note}")

        print(f"\n{Colors.CYAN}{Colors.BRIGHT}{'='*51}{Colors.RESET}")
        print(
            f"{Colors.CYAN}{Colors.BRIGHT}Stage 1: MCP Server Selection (Optional){Colors.RESET}"
        )
        print(f"{Colors.CYAN}{Colors.BRIGHT}{'='*51}{Colors.RESET}")
        print(
            f"\n{Colors.BLUE}MCP servers extend Claude Code with specialized capabilities.{Colors.RESET}"
        )
        print(
            f"{Colors.BLUE}Select servers to configure (you can always add more later):{Colors.RESET}"
        )

        # Add option to skip MCP
        server_options.append("Skip MCP Server installation")

        menu = Menu(
            "Select MCP servers to configure:", server_options, multi_select=True
        )
        selections = menu.display()

        if not selections:
            logger.info("No MCP servers selected")
            return []

        # Filter out the "skip" option and return server keys
        server_keys = list(mcp_servers.keys())
        selected_servers = []

        for i in selections:
            if i < len(server_keys):  # Not the "skip" option
                selected_servers.append(server_keys[i])

        if selected_servers:
            logger.info(f"Selected MCP servers: {', '.join(selected_servers)}")

            # NEW: Collect API keys for selected servers
            collected_keys = collect_api_keys_for_servers(
                selected_servers, mcp_instance
            )

            # Set up environment variables
            if collected_keys:
                setup_environment_variables(collected_keys)

                # Store keys for MCP component to use during installation
                mcp_instance.collected_api_keys = collected_keys
        else:
            logger.info("No MCP servers selected")

        return selected_servers

    except Exception as e:
        logger.error(f"Error in MCP server selection: {e}")
        return []


def select_framework_components(
    registry: ComponentRegistry,
    config_manager: ConfigService,
    selected_mcp_servers: List[str],
) -> List[str]:
    """Stage 2: Framework Component Selection"""
    logger = get_logger()

    try:
        # Framework components (excluding MCP-related ones)
        framework_components = ["core", "modes", "commands", "agents"]

        # Create component menu
        component_options = []
        component_info = {}

        for component_name in framework_components:
            metadata = registry.get_component_metadata(component_name)
            if metadata:
                description = metadata.get("description", "No description")
                component_options.append(f"{component_name} - {description}")
                component_info[component_name] = metadata

        # Add MCP documentation option
        if selected_mcp_servers:
            mcp_docs_desc = f"MCP documentation for {', '.join(selected_mcp_servers)} (auto-selected)"
            component_options.append(f"mcp_docs - {mcp_docs_desc}")
            auto_selected_mcp_docs = True
        else:
            component_options.append(
                "mcp_docs - MCP server documentation (none selected)"
            )
            auto_selected_mcp_docs = False

        print(f"\n{Colors.CYAN}{Colors.BRIGHT}{'='*51}{Colors.RESET}")
        print(
            f"{Colors.CYAN}{Colors.BRIGHT}Stage 2: Framework Component Selection{Colors.RESET}"
        )
        print(f"{Colors.CYAN}{Colors.BRIGHT}{'='*51}{Colors.RESET}")
        print(
            f"\n{Colors.BLUE}Select SuperClaude framework components to install:{Colors.RESET}"
        )

        menu = Menu(
            "Select components (Core is recommended):",
            component_options,
            multi_select=True,
        )
        selections = menu.display()

        if not selections:
            # Default to core if nothing selected
            logger.info("No components selected, defaulting to core")
            selected_components = ["core"]
        else:
            selected_components = []
            all_components = framework_components + ["mcp_docs"]

            for i in selections:
                if i < len(all_components):
                    selected_components.append(all_components[i])

        # Auto-select MCP docs if not explicitly deselected and we have MCP servers
        if auto_selected_mcp_docs and "mcp_docs" not in selected_components:
            # Check if user explicitly deselected it
            mcp_docs_index = len(framework_components)  # Index of mcp_docs in the menu
            if mcp_docs_index not in selections:
                # User didn't select it, but we auto-select it
                selected_components.append("mcp_docs")
                logger.info("Auto-selected MCP documentation for configured servers")

        # Always include MCP component if servers were selected
        if selected_mcp_servers and "mcp" not in selected_components:
            selected_components.append("mcp")

        logger.info(f"Selected framework components: {', '.join(selected_components)}")
        return selected_components

    except Exception as e:
        logger.error(f"Error in framework component selection: {e}")
        return ["core"]  # Fallback to core


def interactive_component_selection(
    registry: ComponentRegistry, config_manager: ConfigService
) -> Optional[List[str]]:
    """Two-stage interactive component selection"""
    logger = get_logger()

    try:
        print(f"\n{Colors.CYAN}SuperClaude Interactive Installation{Colors.RESET}")
        print(
            f"{Colors.BLUE}Select components to install using the two-stage process:{Colors.RESET}"
        )

        # Stage 1: MCP Server Selection
        selected_mcp_servers = select_mcp_servers(registry)

        # Stage 2: Framework Component Selection
        selected_components = select_framework_components(
            registry, config_manager, selected_mcp_servers
        )

        # Store selected MCP servers for components to use
        if not hasattr(config_manager, "_installation_context"):
            config_manager._installation_context = {}
        config_manager._installation_context["selected_mcp_servers"] = (
            selected_mcp_servers
        )

        return selected_components

    except Exception as e:
        logger.error(f"Error in component selection: {e}")
        return None


def display_installation_plan(
    components: List[str], registry: ComponentRegistry, install_dir: Path
) -> None:
    """Display installation plan"""
    logger = get_logger()

    print(f"\n{Colors.CYAN}{Colors.BRIGHT}Installation Plan{Colors.RESET}")
    print("=" * 50)

    # Resolve dependencies
    try:
        ordered_components = registry.resolve_dependencies(components)

        print(f"{Colors.BLUE}Installation Directory:{Colors.RESET} {install_dir}")
        print(f"{Colors.BLUE}Components to install:{Colors.RESET}")

        total_size = 0
        for i, component_name in enumerate(ordered_components, 1):
            metadata = registry.get_component_metadata(component_name)
            if metadata:
                description = metadata.get("description", "No description")
                print(f"  {i}. {component_name} - {description}")

                # Get size estimate if component supports it
                try:
                    instance = registry.get_component_instance(
                        component_name, install_dir
                    )
                    if instance and hasattr(instance, "get_size_estimate"):
                        size = instance.get_size_estimate()
                        total_size += size
                except Exception:
                    pass
            else:
                print(f"  {i}. {component_name} - Unknown component")

        if total_size > 0:
            print(
                f"\n{Colors.BLUE}Estimated size:{Colors.RESET} {format_size(total_size)}"
            )

        print()

    except Exception as e:
        logger.error(f"Could not resolve dependencies: {e}")
        raise


def run_system_diagnostics(validator: Validator) -> None:
    """Run comprehensive system diagnostics"""
    logger = get_logger()

    print(f"\n{Colors.CYAN}{Colors.BRIGHT}SuperClaude System Diagnostics{Colors.RESET}")
    print("=" * 50)

    # Run diagnostics
    diagnostics = validator.diagnose_system()

    # Display platform info
    print(f"{Colors.BLUE}Platform:{Colors.RESET} {diagnostics['platform']}")

    # Display check results
    print(f"\n{Colors.BLUE}System Checks:{Colors.RESET}")
    all_passed = True

    for check_name, check_info in diagnostics["checks"].items():
        status = check_info["status"]
        message = check_info["message"]

        if status == "pass":
            print(f"  ✅ {check_name}: {message}")
        else:
            print(f"  ❌ {check_name}: {message}")
            all_passed = False

    # Display issues and recommendations
    if diagnostics["issues"]:
        print(f"\n{Colors.YELLOW}Issues Found:{Colors.RESET}")
        for issue in diagnostics["issues"]:
            print(f"  ⚠️  {issue}")

        print(f"\n{Colors.CYAN}Recommendations:{Colors.RESET}")
        for recommendation in diagnostics["recommendations"]:
            print(recommendation)

    # Summary
    if all_passed:
        print(
            f"\n{Colors.GREEN}✅ All system checks passed! Your system is ready for superclaude.{Colors.RESET}"
        )
    else:
        print(
            f"\n{Colors.YELLOW}⚠️  Some issues found. Please address the recommendations above.{Colors.RESET}"
        )

    print(f"\n{Colors.BLUE}Next steps:{Colors.RESET}")
    if all_passed:
        print("  1. Run 'superclaude install' to proceed with installation")
        print(
            "  2. Choose your preferred installation mode (quick, minimal, or custom)"
        )
    else:
        print("  1. Install missing dependencies using the commands above")
        print("  2. Restart your terminal after installing tools")
        print("  3. Run 'superclaude install --diagnose' again to verify")


def perform_installation(
    components: List[str],
    args: argparse.Namespace,
    config_manager: ConfigService = None,
) -> bool:
    """Perform the actual installation"""
    logger = get_logger()
    start_time = time.time()

    try:
        # Create installer
        installer = Installer(args.install_dir, dry_run=args.dry_run)

        # Create component registry
        registry = ComponentRegistry(PROJECT_ROOT / "setup" / "components")
        registry.discover_components()

        # Create component instances
        component_instances = registry.create_component_instances(
            components, args.install_dir
        )

        if not component_instances:
            logger.error("No valid component instances created")
            return False

        # Register components with installer
        installer.register_components(list(component_instances.values()))

        # The 'components' list is already resolved, so we can use it directly.
        ordered_components = components

        # Setup progress tracking
        progress = ProgressBar(
            total=len(ordered_components), prefix="Installing: ", suffix=""
        )

        # Install components
        logger.info(f"Installing {len(ordered_components)} components...")

        config = {
            "force": args.force,
            "backup": not args.no_backup,
            "dry_run": args.dry_run,
            "selected_mcp_servers": getattr(
                config_manager, "_installation_context", {}
            ).get("selected_mcp_servers", []),
        }

        success = installer.install_components(ordered_components, config)

        # Update progress
        for i, component_name in enumerate(ordered_components):
            if component_name in installer.installed_components:
                progress.update(i + 1, f"Installed {component_name}")
            else:
                progress.update(i + 1, f"Failed {component_name}")
            time.sleep(0.1)  # Brief pause for visual effect

        progress.finish("Installation complete")

        # Show results
        duration = time.time() - start_time

        if success:
            logger.success(
                f"Installation completed successfully in {duration:.1f} seconds"
            )

            # Show summary
            summary = installer.get_installation_summary()
            if summary["installed"]:
                logger.info(f"Installed components: {', '.join(summary['installed'])}")

            if summary["backup_path"]:
                logger.info(f"Backup created: {summary['backup_path']}")

        else:
            logger.error(
                f"Installation completed with errors in {duration:.1f} seconds"
            )

            summary = installer.get_installation_summary()
            if summary["failed"]:
                logger.error(f"Failed components: {', '.join(summary['failed'])}")

        return success

    except Exception as e:
        logger.exception(f"Unexpected error during installation: {e}")
        return False


def run(args: argparse.Namespace) -> int:
    """Execute installation operation with parsed arguments"""
    operation = InstallOperation()
    operation.setup_operation_logging(args)
    logger = get_logger()
    # ✅ Enhanced security validation with symlink protection
    expected_home = get_home_directory().resolve()
    install_dir_original = args.install_dir
    install_dir_resolved = args.install_dir.resolve()

    # Check for symlink attacks - compare original vs resolved paths
    try:
        # Verify the resolved path is still within user home
        install_dir_resolved.relative_to(expected_home)

        # Additional check: if there's a symlink in the path, verify it doesn't escape user home
        if install_dir_original != install_dir_resolved:
            # Path contains symlinks - verify each component stays within user home
            current_path = expected_home
            parts = install_dir_original.parts
            home_parts = expected_home.parts

            # Skip home directory parts
            if len(parts) >= len(home_parts) and parts[: len(home_parts)] == home_parts:
                relative_parts = parts[len(home_parts) :]

                for part in relative_parts:
                    current_path = current_path / part
                    if current_path.is_symlink():
                        symlink_target = current_path.resolve()
                        # Ensure symlink target is also within user home
                        symlink_target.relative_to(expected_home)
    except ValueError:
        print(f"\n[x] Installation must be inside your user profile directory.")
        print(f"    Expected prefix: {expected_home}")
        print(f"    Provided path:   {install_dir_resolved}")
        print(f"    Security: Symlinks outside user directory are not allowed.")
        sys.exit(1)
    except Exception as e:
        print(f"\n[x] Security validation failed: {e}")
        print(f"    Please use a standard directory path within your user profile.")
        sys.exit(1)

    try:
        # Validate global arguments
        success, errors = operation.validate_global_args(args)
        if not success:
            for error in errors:
                logger.error(error)
            return 1

        # Display header
        if not args.quiet:
            from setup.cli.base import __version__

            display_header(
                f"SuperClaude Installation v{__version__}",
                "Installing SuperClaude framework components",
            )

        # Handle special modes
        if args.list_components:
            registry = ComponentRegistry(PROJECT_ROOT / "setup" / "components")
            registry.discover_components()

            components = registry.list_components()
            if components:
                print(f"\n{Colors.CYAN}Available Components:{Colors.RESET}")
                for component_name in components:
                    metadata = registry.get_component_metadata(component_name)
                    if metadata:
                        desc = metadata.get("description", "No description")
                        category = metadata.get("category", "unknown")
                        print(f"  {component_name} ({category}) - {desc}")
                    else:
                        print(f"  {component_name} - Unknown component")
            else:
                print("No components found")
            return 0

        # Handle diagnostic mode
        if args.diagnose:
            validator = Validator()
            run_system_diagnostics(validator)
            return 0

        # Create component registry and load configuration
        logger.info("Initializing installation system...")

        registry = ComponentRegistry(PROJECT_ROOT / "setup" / "components")
        registry.discover_components()

        config_manager = ConfigService(DATA_DIR)
        validator = Validator()

        # Validate configuration
        config_errors = config_manager.validate_config_files()
        if config_errors:
            logger.error("Configuration validation failed:")
            for error in config_errors:
                logger.error(f"  - {error}")
            return 1

        # Get components to install
        components_to_install = get_components_to_install(
            args, registry, config_manager
        )
        if not components_to_install:
            logger.error("No components selected for installation")
            return 1

        # Resolve dependencies
        try:
            resolved_components = registry.resolve_dependencies(components_to_install)
        except ValueError as e:
            logger.error(f"Dependency resolution error: {e}")
            return 1

        # Validate system requirements for all components
        if not validate_system_requirements(validator, resolved_components):
            if not args.force:
                logger.error("System requirements not met. Use --force to override.")
                return 1
            else:
                logger.warning(
                    "System requirements not met, but continuing due to --force flag"
                )

        # Check for existing installation
        if args.install_dir.exists() and not args.force:
            if not args.dry_run:
                logger.warning(
                    f"Installation directory already exists: {args.install_dir}"
                )
                if not args.yes and not confirm(
                    "Continue and update existing installation?", default=False
                ):
                    logger.info("Installation cancelled by user")
                    return 0

        # Display installation plan
        if not args.quiet:
            display_installation_plan(resolved_components, registry, args.install_dir)

            if not args.dry_run:
                if not args.yes and not confirm(
                    "Proceed with installation?", default=True
                ):
                    logger.info("Installation cancelled by user")
                    return 0

        # Perform installation
        success = perform_installation(resolved_components, args, config_manager)

        if success:
            if not args.quiet:
                display_success("SuperClaude installation completed successfully!")

                if not args.dry_run:
                    print(f"\n{Colors.CYAN}Next steps:{Colors.RESET}")
                    print(f"1. Restart your Claude Code session")
                    print(f"2. Framework files are now available in {args.install_dir}")
                    print(f"3. Use SuperClaude commands and features in Claude Code")

            return 0
        else:
            display_error("Installation failed. Check logs for details.")
            return 1

    except KeyboardInterrupt:
        print(f"\n{Colors.YELLOW}Installation cancelled by user{Colors.RESET}")
        return 130
    except Exception as e:
        return operation.handle_operation_error("install", e)
