"""``ilum module`` sub-app — enable, disable, list, show, status."""

from __future__ import annotations

import typer

import ilum.cli.output as output_mod
from ilum.cli.completers import complete_module_names
from ilum.cli.defaults import resolve_profile_defaults
from ilum.config.manager import ConfigManager
from ilum.config.paths import IlumPaths
from ilum.constants import DEFAULT_CHART_REF, is_local_chart
from ilum.core.helm import HelmClient
from ilum.core.kubernetes import KubeClient
from ilum.core.modules import ModuleCategory, ModuleResolver
from ilum.core.release import ReleaseManager
from ilum.errors import IlumError
from ilum.wizard.deps import ensure_tools

module_app = typer.Typer(help="Manage Ilum platform modules.", no_args_is_help=True)


def _build_manager(
    context: str,
    namespace: str,
    timeout: str,
) -> ReleaseManager:
    paths = IlumPaths.default()
    paths.ensure_dirs()
    return ReleaseManager(
        helm=HelmClient(kubecontext=context, namespace=namespace, timeout=timeout),
        k8s=KubeClient(kubecontext=context),
        resolver=ModuleResolver(),
        config_mgr=ConfigManager(paths),
        paths=paths,
    )


@module_app.command()
def enable(
    modules: list[str] = typer.Argument(..., help="Modules to enable."),  # noqa: B008
    release: str | None = typer.Option(
        None, "--release", "-r", show_default="from profile", help="Helm release name."
    ),
    namespace: str | None = typer.Option(
        None, "--namespace", "-n", show_default="from profile", help="Kubernetes namespace."
    ),
    context: str | None = typer.Option(
        None, "--context", show_default="from profile", help="Kubernetes context."
    ),
    set_flags: list[str] | None = typer.Option(  # noqa: B008
        None,
        "--set",
        help="Additional Helm --set flag (repeatable).",
    ),
    chart: str = typer.Option(
        "", "--chart", help="Chart reference or local path (default: ilum/ilum)."
    ),
    devel: bool = typer.Option(False, "--devel", help="Include pre-release chart versions."),
    reset_defaults: bool = typer.Option(
        False,
        "--reset-defaults",
        help="Reset to new chart defaults before applying user values."
        " Use when upgrading across chart versions with breaking changes.",
    ),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without executing."),
    timeout: str = typer.Option("10m", "--timeout", help="Helm timeout."),
) -> None:
    """Enable one or more modules on a running Ilum release."""
    release, namespace, context = resolve_profile_defaults(release, namespace, context)
    console = output_mod.console

    try:
        ensure_tools(["helm"], console, interactive=not yes)
        mgr = _build_manager(context, namespace, timeout)
        chart_ref = chart or DEFAULT_CHART_REF
        if not is_local_chart(chart_ref):
            mgr.ensure_repo()

        plan = mgr.plan_enable(
            release=release,
            chart=chart_ref,
            module_names=modules,
            set_flags=set_flags,
            devel=devel,
            reset_defaults=reset_defaults,
        )

        # Show summary
        summary_rows = [
            ["Action", "Enable"],
            ["Release", release],
            ["Namespace", namespace],
            ["Modules", ", ".join(modules)],
        ]
        console.operation_summary(summary_rows, title="Enable Summary")

        # Show conflict warnings
        if plan.warnings:
            for w in plan.warnings:
                console.warning(w)
            raise typer.Exit(code=1)

        # Show auto-resolved dependencies
        resolved_deps: set[str] = set()
        for mod_name in modules:
            deps: set[str] = set()
            mgr.resolver._collect_all_deps(mod_name, deps)
            deps.discard(mod_name)
            resolved_deps.update(deps)
        auto_deps = resolved_deps - set(modules)
        if auto_deps:
            console.info(f"Auto-resolved dependencies: {', '.join(sorted(auto_deps))}")

        # Show drift
        if plan.drift and plan.drift.has_drift and plan.drift.diff:
            console.warning("External changes detected:")
            console.diff_table(plan.drift.diff, title="External Changes (Drift)")

        # Show diff — "no values changes" early return takes priority over dry-run
        if plan.effective_diff and not plan.effective_diff.is_empty:
            console.diff_table(plan.effective_diff)
        else:
            console.info("No values changes needed.")
            return

        console.command_preview(mgr.preview_command(plan))

        if dry_run:
            console.info("Dry-run mode — no changes applied.")
            return

        if not yes and not console.confirm("Proceed?"):
            console.info("Aborted.")
            raise typer.Exit()

        # Pre-install CRDs if needed
        crds_needed = [m for m in modules if mgr.resolver.get(m).crd_chart]
        if crds_needed:
            console.info(f"Installing CRDs for: {', '.join(crds_needed)}")
            mgr.ensure_module_crds(crds_needed)

        from ilum.cli.progress import execute_with_progress

        execute_with_progress(mgr, plan, console, message="Enabling modules...")

        # Update config
        current = mgr.get_enabled_modules(release=release)
        combined = list(dict.fromkeys(current + modules))
        mgr.save_enabled_modules(combined, release=release)

        console.success(f"Enabled: {', '.join(modules)}")

    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc


@module_app.command()
def disable(
    modules: list[str] = typer.Argument(..., help="Modules to disable."),  # noqa: B008
    release: str | None = typer.Option(
        None, "--release", "-r", show_default="from profile", help="Helm release name."
    ),
    namespace: str | None = typer.Option(
        None, "--namespace", "-n", show_default="from profile", help="Kubernetes namespace."
    ),
    context: str | None = typer.Option(
        None, "--context", show_default="from profile", help="Kubernetes context."
    ),
    set_flags: list[str] | None = typer.Option(  # noqa: B008
        None,
        "--set",
        help="Additional Helm --set flag (repeatable).",
    ),
    chart: str = typer.Option(
        "", "--chart", help="Chart reference or local path (default: ilum/ilum)."
    ),
    devel: bool = typer.Option(False, "--devel", help="Include pre-release chart versions."),
    reset_defaults: bool = typer.Option(
        False,
        "--reset-defaults",
        help="Reset to new chart defaults before applying user values."
        " Use when upgrading across chart versions with breaking changes.",
    ),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without executing."),
    timeout: str = typer.Option("10m", "--timeout", help="Helm timeout."),
) -> None:
    """Disable one or more modules on a running Ilum release."""
    release, namespace, context = resolve_profile_defaults(release, namespace, context)
    console = output_mod.console

    try:
        ensure_tools(["helm"], console, interactive=not yes)
        mgr = _build_manager(context, namespace, timeout)
        chart_ref = chart or DEFAULT_CHART_REF
        if not is_local_chart(chart_ref):
            mgr.ensure_repo()

        active = frozenset(mgr.get_enabled_modules(release=release))
        plan = mgr.plan_disable(
            release=release,
            chart=chart_ref,
            module_names=modules,
            active_modules=active,
            set_flags=set_flags,
            devel=devel,
            reset_defaults=reset_defaults,
        )

        # Show summary
        summary_rows = [
            ["Action", "Disable"],
            ["Release", release],
            ["Namespace", namespace],
            ["Modules", ", ".join(modules)],
        ]
        console.operation_summary(summary_rows, title="Disable Summary")

        # Warnings for default modules
        for w in plan.warnings:
            console.warning(w)

        # Show drift
        if plan.drift and plan.drift.has_drift and plan.drift.diff:
            console.warning("External changes detected:")
            console.diff_table(plan.drift.diff, title="External Changes (Drift)")

        # Show diff — "no values changes" early return takes priority over dry-run
        if plan.effective_diff and not plan.effective_diff.is_empty:
            console.diff_table(plan.effective_diff)
        else:
            console.info("No values changes needed.")
            return

        console.command_preview(mgr.preview_command(plan))

        if dry_run:
            console.info("Dry-run mode — no changes applied.")
            return

        if not yes and not console.confirm("Proceed?"):
            console.info("Aborted.")
            raise typer.Exit()

        from ilum.cli.progress import execute_with_progress

        execute_with_progress(mgr, plan, console, message="Disabling modules...")

        # Update config
        current = mgr.get_enabled_modules(release=release)
        updated = [m for m in current if m not in modules]
        mgr.save_enabled_modules(updated, release=release)

        console.success(f"Disabled: {', '.join(modules)}")

    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc


@module_app.command()
def status(
    release: str | None = typer.Option(
        None, "--release", "-r", show_default="from profile", help="Helm release name."
    ),
    namespace: str | None = typer.Option(
        None, "--namespace", "-n", show_default="from profile", help="Kubernetes namespace."
    ),
    context: str | None = typer.Option(
        None, "--context", show_default="from profile", help="Kubernetes context."
    ),
    category: str | None = typer.Option(
        None,
        "--category",
        "-c",
        help="Filter by module category.",
    ),
    timeout: str = typer.Option("10m", "--timeout", help="Helm timeout."),
) -> None:
    """Show live module status on a running Ilum release."""
    release, namespace, context = resolve_profile_defaults(release, namespace, context)
    console = output_mod.console
    from ilum.cli.formatters import CommandResult, OutputFormat, ResultFormatter

    fmt = OutputFormat(console.output_format)

    try:
        ensure_tools(["helm"], console)
        mgr = _build_manager(context, namespace, timeout)
        resolver = mgr.resolver

        # Fetch computed values (user-supplied + chart defaults) from release
        live_values = mgr.fetch_computed_values(release)
        live_modules = resolver.detect_enabled_modules(live_values)

        # Get all modules, optionally filtered by category
        if category:
            try:
                cat = ModuleCategory(category.lower())
            except ValueError:
                cats = ", ".join(c.value for c in ModuleCategory)
                console.error(f"Unknown category: {category}. Available: {cats}")
                raise typer.Exit(code=1) from None
            all_mods = resolver.by_category(cat)
        else:
            all_mods = resolver.all_modules()

        # Machine-readable output
        if fmt != OutputFormat.TABLE:
            status_data = []
            for mod in all_mods:
                is_enabled = mod.name in live_modules
                mod_info: dict = {
                    "name": mod.name,
                    "category": mod.category.value,
                    "enabled": is_enabled,
                }
                if is_enabled and mod.pod_label:
                    try:
                        pods_list = mgr.k8s.list_pods_by_label(namespace, mod.pod_label)
                        mod_info["pods_ready"] = sum(1 for p in pods_list if p.ready)
                        mod_info["pods_total"] = len(pods_list)
                    except IlumError:
                        mod_info["pods_ready"] = 0
                        mod_info["pods_total"] = 0
                status_data.append(mod_info)

            result = CommandResult(
                data=status_data,
                summary=f"{len([s for s in status_data if s.get('enabled')])} modules enabled",
            )
            ResultFormatter().format(result, fmt, console)
            return

        # Build status table (Rich TABLE format)
        rows: list[list[str]] = []
        for mod in all_mods:
            is_enabled = mod.name in live_modules
            status_str = "[green]Enabled[/green]" if is_enabled else "[dim]Disabled[/dim]"

            if is_enabled and mod.pod_label:
                # Query pods for this module
                label = mod.pod_label
                try:
                    pods = mgr.k8s.list_pods_by_label(namespace, label)
                    total = len(pods)
                    ready = sum(1 for p in pods if p.ready)
                    pods_str = f"{ready}/{total}"
                    if total == 0:
                        health_str = "[dim]\u2014[/dim]"
                    elif ready == total:
                        health_str = "[green]Healthy[/green]"
                    else:
                        health_str = "[yellow]Degraded[/yellow]"
                except IlumError:
                    pods_str = "?"
                    health_str = "?"
            elif is_enabled:
                # Enabled but no pod_label configured — skip pod query
                pods_str = "[dim]\u2014[/dim]"
                health_str = "[dim]\u2014[/dim]"
            else:
                pods_str = "[dim]\u2014[/dim]"
                health_str = "[dim]\u2014[/dim]"

            rows.append([mod.name, mod.category.value, status_str, pods_str, health_str])

        console.table(
            f"Module Status (release: {release}, namespace: {namespace})",
            ["Module", "Category", "Status", "Pods", "Health"],
            rows,
        )

        # Config drift detection
        config_modules = set(mgr.get_enabled_modules(release=release))
        live_set = set(live_modules)

        if not config_modules and live_set:
            # First run on an existing cluster — auto-sync config from live state
            mgr.save_enabled_modules(sorted(live_set), release=release)
            console.info(f"Synced {len(live_set)} live modules to CLI config.")
        else:
            for mod_name in sorted(config_modules - live_set):
                console.warning(
                    f"Config drift: '{mod_name}' tracked in config but disabled on cluster"
                )
            for mod_name in sorted(live_set - config_modules):
                console.warning(
                    f"Config drift: '{mod_name}' enabled on cluster but not tracked in config"
                )

    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc


@module_app.command("list")
def list_modules(
    category: str | None = typer.Option(
        None,
        "--category",
        "-c",
        help="Filter by category.",
    ),
    enabled: bool | None = typer.Option(
        None,
        "--enabled/--disabled",
        help="Filter by default-enabled status.",
    ),
    search: str | None = typer.Option(
        None,
        "--search",
        "-s",
        help="Filter by substring match on name or description.",
    ),
) -> None:
    """List all available Ilum modules."""
    console = output_mod.console
    resolver = ModuleResolver()

    if category:
        try:
            cat = ModuleCategory(category.lower())
        except ValueError:
            cats = ", ".join(c.value for c in ModuleCategory)
            console.error(f"Unknown category: {category}. Available: {cats}")
            raise typer.Exit(code=1) from None
        modules = resolver.by_category(cat)
    else:
        modules = resolver.all_modules()

    if enabled is not None:
        modules = [m for m in modules if m.default_enabled is enabled]

    if search:
        search_lower = search.lower()
        modules = [
            m
            for m in modules
            if search_lower in m.name.lower() or search_lower in m.description.lower()
        ]

    from ilum.cli.formatters import CommandResult, OutputFormat, ResultFormatter

    fmt = OutputFormat(console.output_format)

    if fmt != OutputFormat.TABLE:
        result = CommandResult(
            data=[
                {
                    "name": m.name,
                    "category": m.category.value,
                    "description": m.description,
                    "default_enabled": m.default_enabled,
                }
                for m in modules
            ],
            summary=f"{len(modules)} modules",
        )
        ResultFormatter().format(result, fmt, console)
        return

    rows = [
        [
            m.name,
            m.category.value,
            m.description,
            "\u2713" if m.default_enabled else "",
        ]
        for m in modules
    ]
    console.table(
        "Ilum Modules",
        ["Name", "Category", "Description", "Default"],
        rows,
    )


@module_app.command()
def tree(
    name: str = typer.Argument(..., help="Module name to show dependency tree for."),
) -> None:
    """Show the dependency tree for a module with resource estimates."""
    console = output_mod.console
    resolver = ModuleResolver()

    try:
        mod = resolver.get(name)
    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc

    from rich.tree import Tree

    from ilum.core.resources import format_estimate, get_module_estimate

    est = get_module_estimate(name)
    root = Tree(f"[bold]{mod.name}[/bold] ({format_estimate(est)})")

    seen: set[str] = {name}

    def _add_deps(parent_tree: Tree, module_name: str) -> None:
        try:
            m = resolver.get(module_name)
        except IlumError:
            return
        for dep_name in sorted(m.requires):
            dep_est = get_module_estimate(dep_name)
            label = f"{dep_name} ({format_estimate(dep_est)})"
            if dep_name in seen:
                parent_tree.add(f"[dim]{label} (shared)[/dim]")
            else:
                seen.add(dep_name)
                branch = parent_tree.add(label)
                _add_deps(branch, dep_name)

    _add_deps(root, name)
    console._console.print(root)


@module_app.command()
def show(
    name: str = typer.Argument(
        ..., help="Module name to inspect.", autocompletion=complete_module_names
    ),
) -> None:
    """Show detailed information about a module."""
    console = output_mod.console
    resolver = ModuleResolver()

    try:
        mod = resolver.get(name)
    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc

    from ilum.cli.formatters import CommandResult, OutputFormat, ResultFormatter

    fmt = OutputFormat(console.output_format)

    if fmt != OutputFormat.TABLE:
        all_flags = resolver.resolve_enables(name)
        result = CommandResult(
            data={
                "name": mod.name,
                "category": mod.category.value,
                "description": mod.description,
                "default_enabled": mod.default_enabled,
                "enable_flags": mod.enable_flags,
                "disable_flags": mod.disable_flags,
                "requires": list(mod.requires),
                "conflicts_with": list(mod.conflicts_with),
                "resolved_enable_flags": all_flags,
            },
            summary=mod.name,
        )
        ResultFormatter().format(result, fmt, console)
        return

    lines = [
        f"[bold]{mod.name}[/bold] ({mod.category.value})",
        f"  {mod.description}",
        "",
        f"  Default enabled: {'yes' if mod.default_enabled else 'no'}",
        f"  Enable flags:  {', '.join(mod.enable_flags)}",
        f"  Disable flags: {', '.join(mod.disable_flags)}",
    ]

    if mod.requires:
        lines.append(f"  Requires: {', '.join(mod.requires)}")
    if mod.conflicts_with:
        lines.append(f"  Conflicts: {', '.join(mod.conflicts_with)}")

    # Resolved dependency chain
    all_flags = resolver.resolve_enables(name)
    if len(all_flags) > len(mod.enable_flags):
        lines.append("")
        lines.append("  Resolved enable chain (including dependencies):")
        for flag in all_flags:
            lines.append(f"    --set {flag}")

    # Resource estimate
    from ilum.core.resources import format_estimate, get_module_estimate

    est = get_module_estimate(name)
    lines.append("")
    lines.append(f"  Resource estimate: {format_estimate(est)}")

    console.panel("\n".join(lines), title=f"Module: {mod.name}")
