"""
Main Installer Orchestrator for AiCippy.

Coordinates the complete installation workflow including:
- Prerequisite checks and dependency installation
- Permission configuration
- Environment variable setup
- Version management and auto-updates
- Progress visualization
"""

from __future__ import annotations

import asyncio
import subprocess
import sys
from dataclasses import dataclass, field
from typing import Any, AsyncIterator, Final

from rich.console import Console

from aicippy.utils.logging import get_logger

logger = get_logger(__name__)

from aicippy.installer.dependency_manager import (
    check_dependencies,
    compare_versions,
    install_all_dependencies,
    verify_installation as verify_deps,
)
from aicippy.installer.env_manager import (
    get_env_export_commands,
    setup_all_env_variables,
    verify_env_variables,
)
from aicippy.installer.permission_manager import (
    setup_all_directories,
    verify_all_permissions,
)
from aicippy.installer.platform_detect import (
    MIN_DISK_SPACE_GB,
    MIN_PYTHON_VERSION,
    PlatformInfo,
    detect_platform,
)
# progress_ui module kept for backward compatibility; not used for minimal UX
# from aicippy.installer.progress_ui import INSTALLATION_PHASES, InstallationProgress

# Version information — sourced from the canonical __version__ in aicippy/__init__.py
from aicippy import __version__ as CURRENT_VERSION
# Fallback URL kept for future use; PyPI via pip is the primary version check method.
VERSION_CHECK_URL: Final[str] = "https://api.aicippy.com/version"
PYPI_PACKAGE_NAME: Final[str] = "aicippy"


@dataclass
class InstallationResult:
    """Result of installation process."""

    success: bool
    version: str
    phases_completed: list[str]
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    requires_restart: bool = False
    env_commands: list[str] = field(default_factory=list)


@dataclass
class VersionInfo:
    """Version information."""

    current: str
    latest: str | None
    update_available: bool
    release_notes: str | None = None


async def check_latest_version() -> VersionInfo:
    """
    Check for the latest available version.

    Returns:
        VersionInfo with current and latest versions.
    """
    try:
        # Try PyPI first
        result = subprocess.run(
            [sys.executable, "-m", "pip", "index", "versions", PYPI_PACKAGE_NAME],
            capture_output=True,
            text=True,
            timeout=30,
        )

        if result.returncode == 0:
            # Parse output to find latest version
            # pip output format:
            #   aicippy (1.0.0)
            #   Available versions: 1.0.0, 0.9.0, ...
            #     INSTALLED: 1.0.0
            #     LATEST:    1.0.0
            output = result.stdout
            if "Available versions:" in output:
                # Get only the first line after "Available versions:"
                versions_section = output.split("Available versions:")[1]
                first_line = versions_section.split("\n")[0].strip()
                versions = [v.strip() for v in first_line.split(",") if v.strip()]
                if versions:
                    latest = versions[0]
                    # Use semantic version comparison, not string equality
                    is_update = compare_versions(CURRENT_VERSION, latest) < 0
                    return VersionInfo(
                        current=CURRENT_VERSION,
                        latest=latest,
                        update_available=is_update,
                    )
    except Exception as e:
        logger.warning("version_check_failed", error=str(e))

    # Fallback: assume current is latest
    return VersionInfo(
        current=CURRENT_VERSION,
        latest=CURRENT_VERSION,
        update_available=False,
    )


async def perform_upgrade(silent: bool = True) -> tuple[bool, str]:
    """
    Perform package upgrade.

    Args:
        silent: Run in silent mode without output.

    Returns:
        Tuple of (success, message).
    """
    try:
        # Build command with silent flags
        cmd = [sys.executable, "-m", "pip", "install", "--upgrade", PYPI_PACKAGE_NAME]
        if silent:
            cmd.extend(["--quiet", "--disable-pip-version-check", "--no-warn-script-location"])

        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=300,
            stdin=subprocess.DEVNULL,  # Auto-accept, no input required
        )

        if result.returncode == 0:
            return True, "Successfully upgraded AiCippy"
        else:
            return False, f"Upgrade failed: {result.stderr}"

    except subprocess.TimeoutExpired:
        return False, "Upgrade timed out"
    except Exception as e:
        return False, f"Upgrade error: {e}"


def check_prerequisites(platform_info: PlatformInfo) -> tuple[bool, list[str]]:
    """
    Check all prerequisites for installation.

    Args:
        platform_info: Platform information.

    Returns:
        Tuple of (all_met, list of issues).
    """
    issues: list[str] = []

    # Check Python version
    if not platform_info.python_meets_minimum:
        py_ver = platform_info.python_version
        min_ver = MIN_PYTHON_VERSION
        issues.append(
            f"Python {min_ver[0]}.{min_ver[1]}+ required, found {py_ver[0]}.{py_ver[1]}.{py_ver[2]}"
        )

    # Check pip availability
    if platform_info.pip_path is None:
        issues.append("pip not found. Please install pip first.")

    # Check disk space
    if not platform_info.disk_space_sufficient:
        issues.append(
            f"Insufficient disk space: {platform_info.disk_space_gb:.1f}GB available, "
            f"{MIN_DISK_SPACE_GB}GB required"
        )

    # Add any platform-specific errors
    issues.extend(platform_info.errors)

    return len(issues) == 0, issues


async def installation_phase_generator(
    platform_info: PlatformInfo,
    include_deps: bool = True,
) -> AsyncIterator[tuple[str, int, str]]:
    """
    Generate installation phases with progress updates.

    Args:
        platform_info: Platform information.
        include_deps: Whether to install dependencies.

    Yields:
        Tuples of (phase_id, progress_percent, status_message).
    """
    errors: list[str] = []
    warnings: list[str] = []

    # Phase 1: Initialization
    yield "init", 0, "Starting installation..."
    yield "init", 50, "Detecting platform..."
    yield "init", 100, "Platform detected"

    # Phase 2: Prerequisites
    yield "prereq", 0, "Checking prerequisites..."
    prereq_ok, prereq_issues = check_prerequisites(platform_info)

    if not prereq_ok:
        for issue in prereq_issues:
            errors.append(issue)
        yield "prereq", 100, "Prerequisites check failed"
        return

    yield "prereq", 100, "All prerequisites met"

    # Phase 3: Dependencies
    if include_deps:
        yield "deps", 0, "Checking dependencies..."
        dep_check = check_dependencies()

        if not dep_check.all_satisfied:
            yield "deps", 10, "Installing dependencies..."

            success, dep_errors = install_all_dependencies(
                platform_info,
                progress_callback=None,
            )

            if not success:
                errors.extend(dep_errors)

            yield "deps", 100, "Dependencies installed"
        else:
            yield "deps", 100, "All dependencies satisfied"
    else:
        yield "deps", 100, "Skipping dependency check"

    # Phase 4: Permissions
    yield "perms", 0, "Configuring permissions..."
    success, perm_results = setup_all_directories(platform_info)

    for result in perm_results:
        if not result.success:
            errors.append(result.message)

    yield "perms", 100, "Permissions configured"

    # Phase 5: Environment
    yield "env", 0, "Setting up environment..."
    success, env_results, requires_restart = setup_all_env_variables(platform_info)

    for result in env_results:
        if not result.success and result.error:
            warnings.append(f"{result.name}: {result.error}")

    if requires_restart:
        warnings.append("Shell restart required for PATH changes")

    yield "env", 100, "Environment configured"

    # Phase 6: Installation finalization
    yield "install", 0, "Finalizing..."
    yield "install", 100, "Installation complete"

    # Phase 7: Verification
    yield "verify", 0, "Verifying installation..."

    deps_ok, deps_msg = verify_deps()
    if not deps_ok:
        warnings.append(deps_msg)

    perms_ok, perm_issues = verify_all_permissions(platform_info)
    if not perms_ok:
        for _path, issue in perm_issues[:2]:
            warnings.append(issue)

    env_ok, missing_vars = verify_env_variables(platform_info)
    if not env_ok:
        for var in missing_vars[:2]:
            warnings.append(f"{var} not set (restart may be needed)")

    yield "verify", 100, "Verification complete"


async def install_aicippy(
    console: Console | None = None,
    show_progress: bool = True,
    include_deps: bool = True,
    silent: bool = False,
    auto_accept: bool = True,
) -> InstallationResult:
    """
    Perform complete AiCippy installation.

    Args:
        console: Optional Rich console.
        show_progress: Whether to show progress UI.
        include_deps: Whether to install dependencies.
        silent: Run in background silent mode (no output, auto-accept).
        auto_accept: Automatically accept all prompts/terms.

    Returns:
        InstallationResult with status.
    """
    from rich.progress import (
        BarColumn,
        Progress,
        SpinnerColumn,
        TextColumn,
        TimeElapsedColumn,
    )

    # Silent mode overrides show_progress
    if silent:
        show_progress = False

    console = console or Console(quiet=silent)
    platform_info = detect_platform()

    errors: list[str] = []
    warnings: list[str] = []
    phases_completed: list[str] = []
    requires_restart = False

    # Phase weights for overall progress calculation (must match INSTALLATION_PHASES)
    phase_weights = {
        "init": 5,
        "prereq": 10,
        "deps": 40,
        "perms": 10,
        "env": 15,
        "install": 15,
        "verify": 5,
    }
    total_weight = sum(phase_weights.values())

    def _calculate_overall(phase_id: str, phase_pct: int) -> int:
        """Calculate overall progress percentage from phase and its progress."""
        phase_order = list(phase_weights.keys())
        if phase_id not in phase_order:
            return 0
        idx = phase_order.index(phase_id)
        completed_weight = sum(
            phase_weights[phase_order[i]] for i in range(idx)
        )
        current_contribution = phase_weights[phase_id] * (phase_pct / 100)
        return int((completed_weight + current_contribution) / total_weight * 100)

    if show_progress:
        console.print()  # Blank line before progress

        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(
                bar_width=40,
                complete_style="#667eea",
                finished_style="#10b981",
            ),
            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
            TimeElapsedColumn(),
            console=console,
            transient=True,
        ) as progress_bar:
            task = progress_bar.add_task("Installing AiCippy...", total=100)

            try:
                async for phase, pct, status in installation_phase_generator(
                    platform_info, include_deps
                ):
                    overall = _calculate_overall(phase, pct)

                    # Map phase IDs to clean user-facing descriptions
                    phase_labels = {
                        "init": "Detecting platform...",
                        "prereq": "Checking prerequisites...",
                        "deps": "Installing dependencies...",
                        "perms": "Configuring permissions...",
                        "env": "Setting up environment...",
                        "install": "Finalizing...",
                        "verify": "Verifying...",
                    }
                    label = phase_labels.get(phase, "Installing AiCippy...")
                    progress_bar.update(task, description=label, completed=overall)

                    if phase not in phases_completed and pct == 100:
                        phases_completed.append(phase)

                    # Collect errors from the prereq phase
                    if "failed" in status.lower() and phase == "prereq":
                        prereq_ok, prereq_issues = check_prerequisites(platform_info)
                        if not prereq_ok:
                            errors.extend(prereq_issues)

            except Exception as e:
                errors.append(f"Installation error: {e}")

            # Ensure bar shows 100% on completion
            if not errors:
                progress_bar.update(task, description="Complete", completed=100)

        # Single clean completion message
        if len(errors) == 0:
            console.print(
                f"\n  [#10b981]\u2713[/#10b981] [bold]AiCippy v{CURRENT_VERSION} installed.[/bold]"
                f" Run [#667eea]aicippy[/#667eea] to start.\n"
            )
            if requires_restart or any("restart" in w.lower() for w in warnings):
                console.print(
                    "  [dim]Note: Restart your terminal for PATH changes.[/dim]\n"
                )
        else:
            console.print(
                f"\n  [#ef4444]\u2717[/#ef4444] [bold]Installation failed:[/bold]"
                f" {errors[0]}\n"
            )

    else:
        # Silent installation
        async for phase, pct, status in installation_phase_generator(
            platform_info, include_deps
        ):
            if phase not in phases_completed and pct == 100:
                phases_completed.append(phase)

    # Get environment export commands for user reference
    env_commands = get_env_export_commands(platform_info)

    return InstallationResult(
        success=len(errors) == 0,
        version=CURRENT_VERSION,
        phases_completed=phases_completed,
        errors=errors,
        warnings=warnings,
        requires_restart=requires_restart,
        env_commands=env_commands,
    )


async def uninstall_aicippy(
    console: Console | None = None,
    remove_config: bool = False,
) -> tuple[bool, str]:
    """
    Uninstall AiCippy.

    Args:
        console: Optional Rich console.
        remove_config: Whether to remove configuration files.

    Returns:
        Tuple of (success, message).
    """
    console = console or Console()
    platform_info = detect_platform()

    try:
        # Uninstall package
        result = subprocess.run(
            [sys.executable, "-m", "pip", "uninstall", "-y", PYPI_PACKAGE_NAME],
            capture_output=True,
            text=True,
            timeout=120,
        )

        if result.returncode != 0:
            return False, f"Uninstall failed: {result.stderr}"

        # Optionally remove config
        if remove_config:
            import shutil

            config_path = platform_info.config_path
            cache_path = platform_info.cache_path

            if config_path.exists():
                try:
                    shutil.rmtree(config_path)
                except OSError as rmtree_err:
                    logger.warning(
                        "config_removal_failed",
                        path=str(config_path),
                        error=str(rmtree_err),
                    )

            if cache_path.exists():
                try:
                    shutil.rmtree(cache_path)
                except OSError as rmtree_err:
                    logger.warning(
                        "cache_removal_failed",
                        path=str(cache_path),
                        error=str(rmtree_err),
                    )

        return True, "AiCippy uninstalled successfully"

    except subprocess.TimeoutExpired:
        return False, "Uninstall timed out"
    except Exception as e:
        return False, f"Uninstall error: {e}"


async def upgrade_aicippy(
    console: Console | None = None,
    check_only: bool = False,
) -> tuple[bool, str, VersionInfo]:
    """
    Check for and optionally perform upgrade.

    Args:
        console: Optional Rich console.
        check_only: If True, only check for updates without installing.

    Returns:
        Tuple of (success, message, version_info).
    """
    console = console or Console()

    # Check version
    version_info = await check_latest_version()

    if not version_info.update_available:
        return True, f"AiCippy {version_info.current} is up to date", version_info

    if check_only:
        return True, f"Update available: {version_info.current} -> {version_info.latest}", version_info

    # Perform upgrade
    success, message = await perform_upgrade()

    if success:
        # After pip upgrade, the new code is on disk but this process still
        # has the old modules loaded. Report the target version directly.
        return True, f"Upgraded to {version_info.latest}", VersionInfo(
            current=version_info.latest or CURRENT_VERSION,
            latest=version_info.latest,
            update_available=False,
        )

    return False, message, version_info


def verify_installation() -> tuple[bool, list[str]]:
    """
    Verify that AiCippy is properly installed.

    Returns:
        Tuple of (success, list of issues).
    """
    issues: list[str] = []
    platform_info = detect_platform()

    # Check dependencies
    deps_ok, deps_msg = verify_deps()
    if not deps_ok:
        issues.append(f"Dependencies: {deps_msg}")

    # Check permissions
    perms_ok, perm_issues = verify_all_permissions(platform_info)
    if not perms_ok:
        for path, issue in perm_issues:
            issues.append(f"Permission: {issue}")

    # Check environment
    env_ok, missing_vars = verify_env_variables(platform_info)
    if not env_ok:
        for var in missing_vars:
            issues.append(f"Environment: {var}")

    # Check if aicippy command is accessible
    import shutil
    if shutil.which("aicippy") is None:
        issues.append("Command 'aicippy' not found in PATH (may need shell restart)")

    return len(issues) == 0, issues


def get_installation_info() -> dict[str, Any]:
    """
    Get information about the current installation.

    Returns:
        Dictionary with installation details.
    """
    platform_info = detect_platform()

    return {
        "version": CURRENT_VERSION,
        "python_version": ".".join(str(v) for v in platform_info.python_version),
        "platform": platform_info.os_name,
        "architecture": platform_info.architecture.name,
        "shell": platform_info.shell_type.name,
        "config_path": str(platform_info.config_path),
        "cache_path": str(platform_info.cache_path),
        "install_path": str(platform_info.user_install_path),
        "is_admin": platform_info.is_admin,
    }


# Entry point for direct execution
if __name__ == "__main__":
    async def _main() -> None:
        console = Console()
        result = await install_aicippy(console=console, show_progress=True)
        if result.success:
            console.print("\nInstallation completed successfully!")
        else:
            console.print("\nInstallation failed:")
            for error in result.errors:
                console.print(f"  - {error}")

    asyncio.run(_main())
