"""CLI commands for addons — create, destroy, info, list, scale, migrate, credentials, etc."""

from __future__ import annotations

import subprocess

import click

from kuberoku.cli.context import resolve_app
from kuberoku.cli.output import info, render_detail, render_table, success


class _ColonGroup(click.Group):
    """Resolve colon-separated subcommands.

    Handles nested colons: 'credentials:rotate' resolves to 'credentials-rotate'
    if a command with that name exists (for addons:credentials:rotate style).
    Otherwise falls back to splitting on first colon for nested groups.
    """

    def resolve_command(
        self,
        ctx: click.Context,
        args: list[str],
    ) -> tuple[str | None, click.Command | None, list[str]]:
        cmd_name = args[0] if args else None
        if cmd_name and ":" in cmd_name:
            # Try colon-to-dash first (e.g., credentials:rotate -> credentials-rotate)
            dashed = cmd_name.replace(":", "-")
            if self.commands and dashed in self.commands:
                args = [dashed] + args[1:]
            else:
                parts = cmd_name.split(":", 1)
                args = [parts[0], parts[1]] + args[1:]
        return super().resolve_command(ctx, args)


@click.group("addons", cls=_ColonGroup, invoke_without_command=True)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def addons(ctx: click.Context, app_flag: str | None) -> None:
    """Manage addons (postgres, redis, etc.)."""
    ctx.ensure_object(dict)
    ctx.obj["_addons_app"] = app_flag

    if ctx.invoked_subcommand is not None:
        return

    # Default: list addons
    app_name = resolve_app(app_flag)
    factory = ctx.obj["factory"]
    addon_list = factory.addons.list(app_name)

    if not addon_list:
        info("No addons attached.")
        return

    render_table(
        addon_list,
        columns=["instance_name", "addon_type", "status", "env_key"],
    )


@addons.command("create")
@click.argument("addon_type")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--as", "instance_name", default=None, help="Custom instance name.")
@click.option("--cpu", default=None, help="CPU request (e.g., '250m' or '250m/500m').")
@click.option("--memory", default=None, help="Memory request (e.g., '256Mi').")
@click.option("--storage", default=None, help="Storage size (e.g., '1Gi').")
@click.option("--ephemeral", is_flag=True, help="No persistent storage.")
@click.option("--url", "external_url", default=None, help="External addon URL.")
@click.option("--version", default=None, help="Addon version (e.g., '16' for postgres).")
@click.pass_context
def create_cmd(
    ctx: click.Context,
    addon_type: str,
    app_flag: str | None,
    instance_name: str | None,
    cpu: str | None,
    memory: str | None,
    storage: str | None,
    ephemeral: bool,
    external_url: str | None,
    version: str | None,
) -> None:
    """Create a new addon instance."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    addon = factory.addons.create(
        app_name,
        addon_type,
        instance_name=instance_name,
        cpu=cpu,
        memory=memory,
        storage=storage,
        ephemeral=ephemeral,
        external_url=external_url,
        version=version,
        on_step=lambda msg: info(msg),
    )
    success(f"Created {addon.addon_type} addon as '{addon.instance_name}'.")
    render_detail(
        addon.instance_name,
        {
            "Type": addon.addon_type,
            "Status": addon.status,
            "Image": addon.image,
            "Env": addon.env_key,
        },
    )


@addons.command("destroy")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option(
    "--delete-data",
    is_flag=True,
    help="Also delete persistent data (PVC).",
)
@click.option("--confirm", is_flag=True, help="Skip confirmation prompt.")
@click.pass_context
def destroy_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
    delete_data: bool,
    confirm: bool,
) -> None:
    """Destroy an addon instance."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    if not confirm:
        click.confirm(
            f"Destroy addon '{instance_name}' on app '{app_name}'?",
            abort=True,
        )

    factory.addons.destroy(
        app_name,
        instance_name,
        delete_data=delete_data,
        on_step=lambda msg: info(msg),
    )
    success(f"Destroyed addon '{instance_name}'.")
    if not delete_data:
        info("Persistent data (PVC) retained. Use --delete-data to remove.")


@addons.command("info")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def info_cmd(ctx: click.Context, instance_name: str, app_flag: str | None) -> None:
    """Show addon details."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    addon = factory.addons.info(app_name, instance_name)
    fields: dict[str, str] = {
        "Type": addon.addon_type,
        "Status": addon.status,
        "Image": addon.image,
        "CPU": addon.cpu,
        "Memory": addon.memory,
        "Env": addon.env_key,
    }
    if addon.storage:
        fields["Storage"] = addon.storage
    if addon.external:
        fields["External"] = "yes"
    if addon.ephemeral:
        fields["Ephemeral"] = "yes"
    render_detail(addon.instance_name, fields)


@addons.command("scale")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--cpu", default=None, help="New CPU request.")
@click.option("--memory", default=None, help="New memory request.")
@click.pass_context
def scale_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
    cpu: str | None,
    memory: str | None,
) -> None:
    """Scale addon resources (cpu/memory)."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    addon = factory.addons.scale(
        app_name,
        instance_name,
        cpu=cpu,
        memory=memory,
        on_step=lambda msg: info(msg),
    )
    success(f"Scaled addon '{addon.instance_name}'.")


@addons.command("credentials")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def credentials_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
) -> None:
    """Show addon credentials."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    creds = factory.addons.credentials(app_name, instance_name)
    render_detail(f"{instance_name} credentials", creds)


@addons.command("credentials-rotate")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def credentials_rotate_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
) -> None:
    """Rotate addon credentials (new password)."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    creds = factory.addons.credentials_rotate(
        app_name,
        instance_name,
        on_step=lambda msg: info(msg),
    )
    success(f"Rotated credentials for '{instance_name}'.")
    render_detail(f"{instance_name} credentials", creds)


@addons.command("exec")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def exec_cmd(ctx: click.Context, instance_name: str, app_flag: str | None) -> None:
    """Show exec command for addon access."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    pod_name, cmd = factory.addons.exec_command(app_name, instance_name)
    info(f"kubectl exec -it {pod_name} -n {factory.namespace} -- {' '.join(cmd)}")


@addons.command("backup")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def backup_cmd(ctx: click.Context, instance_name: str, app_flag: str | None) -> None:
    """Run addon backup command."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    output = factory.addons.backup(app_name, instance_name)
    click.echo(output)


# ── migrate commands ─────────────────────────────────────────────
# Flat commands: `addons migrate` and `addons migrate-rollback`.
# Colon syntax: `addons:migrate` and `addons:migrate:rollback` both work
# because _ColonGroup tries colon-to-dash resolution (migrate:rollback ->
# migrate-rollback) before nested group resolution.


@addons.command("migrate")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--version", required=True, help="Target version.")
@click.option("--force", is_flag=True, help="Bypass migration path validation.")
@click.pass_context
def migrate_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
    version: str,
    force: bool,
) -> None:
    """Migrate addon to a new version (with backup + restore)."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    addon = factory.addons.migrate(
        app_name,
        instance_name,
        target_version=version,
        force=force,
        on_step=lambda msg: info(msg),
    )
    success(f"Migrated addon '{addon.instance_name}' to v{version}.")


@addons.command("migrate-rollback")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def migrate_rollback_cmd(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
) -> None:
    """Rollback addon to previous version."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    addon = factory.addons.migrate_rollback(
        app_name,
        instance_name,
        on_step=lambda msg: info(msg),
    )
    success(f"Rolled back addon '{addon.instance_name}' to v{addon.image}.")


# ── expose subgroup ──────────────────────────────────────────────


@addons.group("expose", cls=_ColonGroup, invoke_without_command=True)
@click.pass_context
def addon_expose(ctx: click.Context) -> None:
    """Expose or unexpose an addon externally."""
    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())


@addon_expose.command("on")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option(
    "--method",
    type=click.Choice(["gateway", "loadbalancer", "nodeport"]),
    default="gateway",
    help="Expose method (default: gateway).",
)
@click.option("--port", type=int, default=None, help="Override port.")
@click.option("--wait/--no-wait", default=True, help="Wait for external IP.")
@click.pass_context
def addon_expose_on(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
    method: str,
    port: int | None,
    wait: bool,
) -> None:
    """Expose an addon externally."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    endpoint = factory.addons.expose_on(
        app_name,
        instance_name,
        method=method,
        port=port,
        wait=wait,
        on_step=lambda msg: info(msg),
    )

    if endpoint.gateway_port is not None:
        success(f"Exposed addon '{instance_name}' via gateway port {endpoint.gateway_port}.")
    else:
        success(f"Exposed addon '{instance_name}' via {endpoint.service_type}.")
    info("Warning: Database is now accessible from the internet.")

    fields: dict[str, str] = {
        "Name": endpoint.name,
        "Kind": endpoint.kind,
        "Method": endpoint.method,
    }
    if endpoint.gateway_port is not None:
        fields["Gateway Port"] = str(endpoint.gateway_port)
    if endpoint.external_ip:
        fields["External IP"] = endpoint.external_ip
    if endpoint.hostname:
        fields["Hostname"] = endpoint.hostname
    if endpoint.ports:
        fields["Ports"] = ", ".join(str(p) for p in endpoint.ports)
    render_detail(f"{app_name} expose", fields)


@addon_expose.command("off")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def addon_expose_off(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
) -> None:
    """Revert an exposed addon to internal-only."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    endpoint = factory.addons.expose_off(
        app_name,
        instance_name,
        on_step=lambda msg: info(msg),
    )

    success(f"Unexposed addon '{instance_name}' (now ClusterIP).")
    fields: dict[str, str] = {
        "Name": endpoint.name,
        "Kind": endpoint.kind,
        "Type": endpoint.service_type,
    }
    if endpoint.ports:
        fields["Ports"] = ", ".join(str(p) for p in endpoint.ports)
    render_detail(f"{app_name} unexpose", fields)


# ── connect ──────────────────────────────────────────────────────


@addons.command("connect")
@click.argument("instance_name")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--port", "local_port", type=int, default=None, help="Local port override.")
@click.pass_context
def addon_connect(
    ctx: click.Context,
    instance_name: str,
    app_flag: str | None,
    local_port: int | None,
) -> None:
    """Open a local port-forward to an addon."""
    parent_app = ctx.obj.get("_addons_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    target = factory.addons.connect(app_name, instance_name)

    # Build kubectl port-forward command
    fwd_args = ["kubectl", "port-forward", "-n", target.namespace, f"svc/{target.service_name}"]
    for pm in target.ports:
        lp = local_port if local_port is not None else pm.port
        fwd_args.append(f"{lp}:{pm.port}")
        info(f"Forwarding localhost:{lp} -> {instance_name}:{pm.port}/{pm.protocol.lower()}")
        local_port = None

    info(f"Connecting to addon '{instance_name}' on app '{app_name}'...")
    info("Press Ctrl+C to stop.")

    proc = subprocess.Popen(fwd_args)
    try:
        proc.wait()
    except KeyboardInterrupt:
        proc.terminate()
        proc.wait()
        info("Disconnected.")
