"""Wizard orchestrator: preflight -> cluster -> profile -> modules -> confirm -> save."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

import questionary

from ilum.config.models import ClusterConfig, ProfileConfig
from ilum.constants import DEFAULT_NAMESPACE, DEFAULT_RELEASE_NAME
from ilum.core.modules import ModuleCategory, ModuleResolver
from ilum.core.presets import DeploymentPreset, get_preset, list_presets
from ilum.doctor.checks import CheckStatus
from ilum.wizard.cluster import PRESETS, ClusterManager, ClusterProvider
from ilum.wizard.deps import ToolInstaller

if TYPE_CHECKING:
    from ilum.cli.output import IlumConsole
    from ilum.config.manager import ConfigManager


class InitWizard:
    """Yeoman-inspired wizard: preflight -> cluster -> profile -> modules -> confirm -> save."""

    def __init__(
        self,
        console: IlumConsole,
        config_mgr: ConfigManager,
        resolver: ModuleResolver,
    ) -> None:
        self._console = console
        self._config_mgr = config_mgr
        self._resolver = resolver
        self._installer = ToolInstaller()
        self._cluster_mgr = ClusterManager()

    def run(self, *, non_interactive: bool = False, profile_name: str = "default") -> ProfileConfig:
        """Run the full wizard flow. Returns the created profile."""
        self._console.panel(
            "[bold]Welcome to the Ilum CLI Setup Wizard[/bold]\n\n"
            "This wizard will help you configure your Ilum deployment.",
            title="ilum init",
        )

        # Step 1: Preflight checks
        self._step_preflight(non_interactive=non_interactive)

        # Step 2: Cluster selection / creation
        cluster_config = self._step_cluster(non_interactive=non_interactive)

        # Step 3: Profile settings
        name, release_name, namespace = self._step_profile(
            profile_name=profile_name,
            non_interactive=non_interactive,
        )

        # Step 4: Preset selection
        selected_preset = self._step_preset(non_interactive=non_interactive)

        # Step 5: Module selection (skipped if preset selected)
        if selected_preset:
            modules = list(selected_preset.modules)
            # Resolve dependencies
            modules, dep_msgs = self._resolver.resolve_selection(modules)
            for msg in dep_msgs:
                self._console.info(msg)
            self._console.success(
                f"Preset '{selected_preset.name}' selected: {len(modules)} modules."
            )
        else:
            modules = self._step_modules(non_interactive=non_interactive)

        # Build the profile
        profile = ProfileConfig(
            name=name,
            release_name=release_name,
            cluster=cluster_config,
            enabled_modules=modules,
        )

        # Step 5: Confirm
        if not non_interactive and not self._step_confirm(profile):
            self._console.warning("Setup cancelled.")
            return profile

        # Step 6: Save
        self._step_save(name, profile)

        return profile

    @staticmethod
    def _ask(question: questionary.Question) -> Any:
        """Run prompt; re-raise Ctrl+C instead of returning None."""
        result = question.ask()
        if result is None:
            raise KeyboardInterrupt
        return result

    # -- Steps ---------------------------------------------------------------

    def _step_preflight(self, *, non_interactive: bool = False) -> None:
        """Check and optionally install helm, kubectl, docker."""
        self._console.info("Checking prerequisites...")
        results = self._installer.check_and_install(
            self._console,
            non_interactive=non_interactive,
        )

        failures = [r for r in results if r.status == CheckStatus.FAIL]
        if failures:
            self._console.warning(
                f"{len(failures)} tool(s) missing or outdated. Some features may not work."
            )
        else:
            self._console.success("All prerequisites met.")

    def _step_cluster(self, *, non_interactive: bool = False) -> ClusterConfig:
        """Detect kubecontexts or offer to create a local cluster."""
        contexts = self._list_kubecontexts()

        if non_interactive:
            # Use current context or empty
            current = self._current_kubecontext()
            return ClusterConfig(
                kubecontext=current,
                namespace=DEFAULT_NAMESPACE,
            )

        choices: list[str] = []
        if contexts:
            choices.extend(contexts)
        choices.append(">> Create a new local cluster")

        answer = self._ask(
            questionary.select(
                "Select a Kubernetes context:",
                choices=choices,
            )
        )

        if answer == ">> Create a new local cluster":
            return self._create_cluster_interactive()

        namespace = (
            self._ask(
                questionary.text(
                    "Namespace for Ilum deployment:",
                    default=DEFAULT_NAMESPACE,
                )
            )
            or DEFAULT_NAMESPACE
        )

        return ClusterConfig(kubecontext=answer, namespace=namespace)

    def _step_profile(
        self,
        *,
        profile_name: str = "default",
        non_interactive: bool = False,
    ) -> tuple[str, str, str]:
        """Prompt for profile name, release name, namespace."""
        if non_interactive:
            return profile_name, DEFAULT_RELEASE_NAME, DEFAULT_NAMESPACE

        name = (
            self._ask(
                questionary.text(
                    "Profile name:",
                    default=profile_name,
                )
            )
            or profile_name
        )

        release = (
            self._ask(
                questionary.text(
                    "Helm release name:",
                    default=DEFAULT_RELEASE_NAME,
                )
            )
            or DEFAULT_RELEASE_NAME
        )

        namespace = (
            self._ask(
                questionary.text(
                    "Kubernetes namespace:",
                    default=DEFAULT_NAMESPACE,
                )
            )
            or DEFAULT_NAMESPACE
        )

        return name, release, namespace

    def _step_preset(self, *, non_interactive: bool = False) -> DeploymentPreset | None:
        """Choose a deployment preset or 'Custom' for manual module selection."""
        if non_interactive:
            return None

        presets = list_presets()
        choices = [
            questionary.Choice(
                title=f"{p.name} — {p.description} ({len(p.modules)} modules)",
                value=p.name,
            )
            for p in presets
        ]
        choices.append(questionary.Choice(title="Custom", value="Custom"))

        answer = self._ask(
            questionary.select(
                "Choose a deployment preset:",
                choices=choices,
            )
        )

        if answer == "Custom":
            return None

        return get_preset(answer)

    def _step_modules(self, *, non_interactive: bool = False) -> list[str]:
        """Module selection grouped by category."""
        defaults = self._resolver.default_enabled()

        if non_interactive:
            return defaults

        required = set(self._resolver.required_modules())
        all_modules = self._resolver.all_modules()
        default_set = set(defaults) | required
        all_module_names = {m.name for m in all_modules}

        # Build choices grouped by category
        choices: list[questionary.Choice] = []
        for category in ModuleCategory:
            mods = [m for m in all_modules if m.category is category]
            if not mods:
                continue
            # Category separator
            choices.append(
                questionary.Choice(
                    title=f"── {category.value.upper()} ──",
                    disabled="",
                )
            )
            for mod in mods:
                is_required = mod.name in required
                choices.append(
                    questionary.Choice(
                        title=f"  {mod.name} - {mod.description}"
                        + (" [required]" if is_required else ""),
                        value=mod.name,
                        checked=mod.name in defaults or is_required,
                        disabled="required" if is_required else None,
                    )
                )

        question = questionary.checkbox(
            "Select modules to enable:",
            choices=choices,
            instruction="(↑↓ move, space toggle, a toggle all, d defaults, enter confirm)",
        )

        # Hook into prompt_toolkit so 'd' key immediately resets all
        # selections to the default set while staying in the checkbox.
        self._bind_reset_to_defaults(question, default_set, all_module_names)

        selected: list[str] = self._ask(question)

        # Resolve dependencies and enforce required modules
        final, messages = self._resolver.resolve_selection(selected)
        if messages:
            for msg in messages:
                self._console.info(msg)

        # Check for conflicts
        warnings = self._resolver.validate_combination(frozenset(final))
        if warnings:
            for w in warnings:
                self._console.warning(w)

        # Confirm selection or allow reset / re-select
        action = self._ask(
            questionary.select(
                f"Selected {len(final)} modules. What would you like to do?",
                choices=[
                    "Continue with this selection",
                    "Reset to defaults",
                    "Re-select modules",
                ],
            )
        )

        if action == "Reset to defaults":
            final, _ = self._resolver.resolve_selection(defaults)
            return final

        if action == "Re-select modules":
            return self._step_modules()

        return final

    @staticmethod
    def _bind_reset_to_defaults(
        question: questionary.Question,
        default_set: set[str],
        all_module_names: set[str],
    ) -> None:
        """Add a 'd' keybinding that immediately resets module selections
        to the default set while staying in the checkbox."""
        from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
        from prompt_toolkit.layout import Window
        from questionary.prompts.common import InquirerControl

        app = question.application

        # Walk the layout tree to find the InquirerControl instance.
        ic = None

        def _walk(container: object) -> None:
            nonlocal ic
            if ic is not None:
                return
            if isinstance(container, Window):
                if isinstance(container.content, InquirerControl):
                    ic = container.content
                return
            if hasattr(container, "get_children"):
                for child in container.get_children():
                    _walk(child)

        _walk(app.layout.container)

        if ic is None:
            return

        custom_kb = KeyBindings()

        @custom_kb.add("d", eager=True)
        def _key_d_reset(_event: object) -> None:
            ic.selected_options = [
                c.value
                for c in ic.choices
                if c.value in default_set and c.value in all_module_names
            ]

        # Custom bindings first so they take priority over the originals.
        app.key_bindings = merge_key_bindings([custom_kb, app.key_bindings])

    def _step_confirm(self, profile: ProfileConfig) -> bool:
        """Show a summary table and ask for confirmation."""
        rows = [
            ["Profile", profile.name],
            ["Release Name", profile.release_name],
            ["Kubecontext", profile.cluster.kubecontext or "(current)"],
            ["Namespace", profile.cluster.namespace],
            ["Modules", ", ".join(profile.enabled_modules[:5])],
        ]
        if len(profile.enabled_modules) > 5:
            rows.append(["", f"  ... +{len(profile.enabled_modules) - 5} more"])

        self._console.operation_summary(rows, title="Configuration Summary")

        result: bool = self._ask(questionary.confirm("Save this configuration?", default=True))
        return result

    def _step_save(self, profile_name: str, profile: ProfileConfig) -> None:
        """Write the profile to config.yaml."""
        config = self._config_mgr.ensure_config()

        if profile_name in config.profiles:
            self._console.info(f"Updating existing profile '{profile_name}'")
        else:
            self._console.info(f"Creating new profile '{profile_name}'")

        config.profiles[profile_name] = profile
        config.active_profile = profile_name
        config.updated_at = datetime.now(UTC).isoformat()
        self._config_mgr.save(config)
        self._console.success(f"Configuration saved to {self._config_mgr.config_file}")

    # -- Helpers -------------------------------------------------------------

    def _create_cluster_interactive(self) -> ClusterConfig:
        """Interactively create a local cluster."""
        provider_str = self._ask(
            questionary.select(
                "Cluster provider:",
                choices=["k3d", "minikube", "kind"],
            )
        )

        preset_str = self._ask(
            questionary.select(
                "Resource preset:",
                choices=[
                    questionary.Choice(
                        title=f"{k} (CPUs: {v.cpus}, Memory: {v.memory})",
                        value=k,
                    )
                    for k, v in PRESETS.items()
                ],
            )
        )

        provider = ClusterProvider(provider_str)
        preset = PRESETS[preset_str]

        try:
            record = self._cluster_mgr.create(provider, preset, self._console)

            # Save cluster record to config
            config = self._config_mgr.ensure_config()
            config.clusters.append(record)
            self._config_mgr.save(config)

            return ClusterConfig(
                kubecontext=record.kubecontext,
                namespace=DEFAULT_NAMESPACE,
            )
        except Exception as exc:
            self._console.error(f"Cluster creation failed: {exc}")
            self._console.info("Continuing with current context...")
            return ClusterConfig()

    @staticmethod
    def _list_kubecontexts() -> list[str]:
        """List available kubeconfig contexts."""
        try:
            from kubernetes import config as k8s_config

            contexts, _ = k8s_config.list_kube_config_contexts()
            return [ctx["name"] for ctx in contexts] if contexts else []
        except Exception:
            return []

    @staticmethod
    def _current_kubecontext() -> str:
        """Get the current kubeconfig context name."""
        try:
            from kubernetes import config as k8s_config

            _, current = k8s_config.list_kube_config_contexts()
            return current["name"] if current else ""
        except Exception:
            return ""
