"""``ilum config`` subcommands."""

from __future__ import annotations

import os
import subprocess
import sys

import typer

import ilum.cli.output as output_mod
from ilum.cli.completers import complete_profile_names
from ilum.config.manager import ConfigManager
from ilum.config.paths import IlumPaths

config_app = typer.Typer(help="Manage Ilum CLI configuration.", no_args_is_help=True)


def _manager() -> ConfigManager:
    return ConfigManager(IlumPaths.default())


@config_app.command()
def show(
    key: str | None = typer.Argument(
        None, help="Dot-notation key to display (e.g. active_profile)."
    ),
) -> None:
    """Show the current configuration (or a specific key)."""
    console = output_mod.console
    mgr = _manager()
    config = mgr.load()

    if key:
        value = mgr.get(key)
        if value is None:
            console.error(f"Key not found: {key}")
            raise typer.Exit(code=1)
        typer.echo(value)
    else:
        from io import StringIO

        from ruamel.yaml import YAML

        yaml = YAML()
        yaml.default_flow_style = False
        buf = StringIO()
        yaml.dump(config.model_dump(), buf)
        console.panel(buf.getvalue(), title="ilum config")


@config_app.command("set")
def set_value(
    key: str = typer.Argument(..., help="Dot-notation key (e.g. active_profile)."),
    value: str = typer.Argument(..., help="Value to set."),
) -> None:
    """Set a configuration value."""
    console = output_mod.console
    mgr = _manager()
    mgr.set(key, value)
    console.success(f"{key} = {value}")


@config_app.command()
def path() -> None:
    """Print the configuration file path."""
    mgr = _manager()
    typer.echo(mgr.config_file)


@config_app.command()
def init(
    force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config."),
) -> None:
    """Create a default configuration file."""
    console = output_mod.console
    mgr = _manager()

    if mgr.config_file.exists() and not force:
        console.warning(f"Config already exists: {mgr.config_file}")
        console.info("Use --force to overwrite.")
        return

    mgr.ensure_config()
    console.success(f"Created config: {mgr.config_file}")


@config_app.command()
def edit() -> None:
    """Open the configuration file in $EDITOR."""
    console = output_mod.console
    mgr = _manager()

    if not mgr.config_file.exists():
        mgr.ensure_config()

    _default_editor = "notepad" if sys.platform == "win32" else "vi"
    editor = os.environ.get("EDITOR", os.environ.get("VISUAL", _default_editor))
    try:
        subprocess.run([editor, str(mgr.config_file)], check=True)
    except FileNotFoundError as exc:
        console.error(f"Editor not found: {editor}")
        console.info("Set $EDITOR environment variable.")
        raise typer.Exit(code=1) from exc

    # Post-edit validation
    try:
        mgr.load()
        console.success("Config validated successfully after edit.")
    except Exception as exc:
        console.warning(f"Config validation warning: {exc}")
        console.info("The file was saved but may contain errors.")


@config_app.command()
def use(
    profile_name: str = typer.Argument(
        ..., help="Profile name to switch to.", autocompletion=complete_profile_names
    ),
) -> None:
    """Switch the active profile."""
    console = output_mod.console
    mgr = _manager()

    try:
        config = mgr.load()
    except Exception as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc

    if profile_name not in config.profiles:
        available = ", ".join(sorted(config.profiles.keys())) or "(none)"
        console.error(f"Unknown profile: '{profile_name}'")
        console.info(f"Available profiles: {available}")
        raise typer.Exit(code=1)

    config.active_profile = profile_name
    mgr.save(config)
    console.success(f"Switched to profile '{profile_name}'")


@config_app.command("list-profiles")
def list_profiles() -> None:
    """List all profiles with the active one marked."""
    console = output_mod.console
    mgr = _manager()
    config = mgr.load()

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

    fmt = OutputFormat(console.output_format)

    if fmt != OutputFormat.TABLE:
        data_list = [
            {
                "name": name,
                "active": name == config.active_profile,
                "release": profile.release_name or "",
                "namespace": profile.cluster.namespace or "default",
                "modules": list(profile.enabled_modules),
                "module_count": len(profile.enabled_modules),
            }
            for name, profile in sorted(config.profiles.items())
        ]
        result = CommandResult(data=data_list, summary=f"{len(data_list)} profiles")
        ResultFormatter().format(result, fmt, console)
        return

    rows: list[list[str]] = []
    for name, profile in sorted(config.profiles.items()):
        active = "\u2192" if name == config.active_profile else ""
        modules_count = str(len(profile.enabled_modules))
        ns = profile.cluster.namespace or "default"
        rows.append([active, name, profile.release_name or "(unset)", ns, modules_count])

    console.table(
        "Profiles",
        ["", "Name", "Release", "Namespace", "Modules"],
        rows,
    )


@config_app.command()
def validate() -> None:
    """Validate the config file against the Pydantic model."""
    console = output_mod.console
    mgr = _manager()

    if not mgr.config_file.exists():
        console.warning("No config file found.")
        console.info("Create one with: ilum config init")
        return

    try:
        from ruamel.yaml import YAML

        yaml = YAML()
        with mgr.config_file.open() as f:
            raw_data = yaml.load(f)
    except Exception as exc:
        console.error(f"Failed to parse YAML: {exc}")
        raise typer.Exit(code=1) from exc

    if raw_data is None:
        console.warning("Config file is empty.")
        return

    from pydantic import ValidationError

    from ilum.config.models import IlumConfig

    try:
        IlumConfig.model_validate(raw_data)
        console.success("Config file is valid.")
    except ValidationError as exc:
        console.error("Config validation errors:")
        for error in exc.errors():
            loc = " \u2192 ".join(str(part) for part in error["loc"])
            console.error(f"  {loc}: {error['msg']}")
        raise typer.Exit(code=1) from exc


@config_app.command()
def backup() -> None:
    """Create a timestamped backup of the config file."""
    console = output_mod.console
    mgr = _manager()

    if not mgr.config_file.exists():
        console.warning("No config file to back up.")
        return

    import shutil
    from datetime import UTC, datetime

    timestamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%S")
    backup_path = mgr.config_file.with_suffix(f".{timestamp}.bak")
    shutil.copy2(str(mgr.config_file), str(backup_path))
    console.success(f"Backup created: {backup_path}")


@config_app.command("export")
def export_profile(
    profile_name: str = typer.Argument(..., help="Profile name to export."),
    output_file: str | None = typer.Option(
        None, "--out", "-o", help="Output file path (default: stdout)."
    ),
    include_cluster: bool = typer.Option(
        False, "--include-cluster", help="Include cluster-specific data (context, kubeconfig)."
    ),
) -> None:
    """Export a profile as portable YAML for team sharing."""
    console = output_mod.console
    mgr = _manager()

    try:
        config = mgr.load()
    except Exception as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc

    if profile_name not in config.profiles:
        available = ", ".join(sorted(config.profiles.keys())) or "(none)"
        console.error(f"Unknown profile: '{profile_name}'")
        console.info(f"Available profiles: {available}")
        raise typer.Exit(code=1)

    profile = config.profiles[profile_name]
    export_data = profile.model_dump(mode="json")
    export_data["name"] = profile_name

    if not include_cluster and "cluster" in export_data:
        cluster = export_data["cluster"]
        cluster.pop("kubecontext", None)

    from io import StringIO

    from ruamel.yaml import YAML

    yaml = YAML()
    yaml.default_flow_style = False

    if output_file:
        from pathlib import Path

        out_path = Path(output_file)
        with out_path.open("w") as f:
            yaml.dump(export_data, f)
        console.success(f"Exported profile '{profile_name}' to {out_path}")
    else:
        buf = StringIO()
        yaml.dump(export_data, buf)
        typer.echo(buf.getvalue())


@config_app.command("import")
def import_profile(
    input_file: str = typer.Argument(..., help="Path to the profile YAML file to import."),
    name: str | None = typer.Option(None, "--name", help="Override the profile name."),
    force: bool = typer.Option(
        False, "--force", "-f", help="Overwrite existing profile with the same name."
    ),
) -> None:
    """Import a profile from a YAML file."""
    from pathlib import Path

    from pydantic import ValidationError
    from ruamel.yaml import YAML

    from ilum.config.models import ProfileConfig

    console = output_mod.console
    mgr = _manager()
    in_path = Path(input_file)

    if not in_path.exists():
        console.error(f"File not found: {in_path}")
        raise typer.Exit(code=1)

    try:
        yaml = YAML()
        with in_path.open() as f:
            raw_data = yaml.load(f)
    except Exception as exc:
        console.error(f"Failed to parse YAML: {exc}")
        raise typer.Exit(code=1) from exc

    if raw_data is None:
        console.error("Import file is empty.")
        raise typer.Exit(code=1)

    # Extract profile name
    profile_name = name or raw_data.pop("name", None)
    if not profile_name:
        console.error("No profile name specified. Use --name or include 'name' in the YAML.")
        raise typer.Exit(code=1)

    # Ensure the name field is present for model validation
    raw_data["name"] = profile_name

    # Validate the profile data
    try:
        profile = ProfileConfig.model_validate(raw_data)
    except ValidationError as exc:
        console.error(f"Invalid profile data: {exc}")
        raise typer.Exit(code=1) from exc

    # Load existing config and check for conflicts
    config = mgr.load()
    if profile_name in config.profiles and not force:
        console.error(f"Profile '{profile_name}' already exists. Use --force to overwrite.")
        raise typer.Exit(code=1)

    config.profiles[profile_name] = profile
    mgr.save(config)
    console.success(f"Imported profile '{profile_name}'")
