"""CLI commands for apps:* — create, destroy, info, list, rename, link, status, maintenance."""

from __future__ import annotations

from typing import TYPE_CHECKING

import click

from kuberoku.cli.context import resolve_app
from kuberoku.cli.output import hint, info, render_detail, render_table, success
from kuberoku.config.project import (
    remove_alias,
    remove_project_file,
    resolve_project_link,
    write_project_file,
)

if TYPE_CHECKING:
    from kuberoku.models import AppStatus, Dyno


class _ColonGroup(click.Group):
    """Resolve colon-separated subcommands (link:add, link:remove, etc.)."""

    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:
            parts = cmd_name.split(":", 1)
            args = [parts[0], parts[1]] + args[1:]
        return super().resolve_command(ctx, args)


@click.group("apps", cls=_ColonGroup, invoke_without_command=True)
@click.option("--format", "fmt", type=click.Choice(["table", "json"]), default="table")
@click.pass_context
def apps(ctx: click.Context, fmt: str) -> None:
    """Manage apps."""
    if ctx.invoked_subcommand is None:
        factory = ctx.obj["factory"]
        app_list = factory.apps.list()
        render_table(
            app_list,
            columns=["name", "release_version", "created_at"],
            fmt=fmt,
        )


@apps.command()
@click.argument("name")
@click.option("--cluster", default=None, help="Target cluster for the new app.")
@click.pass_context
def create(ctx: click.Context, name: str, cluster: str | None) -> None:
    """Create a new app."""
    factory = ctx.obj["factory"]
    app = factory.apps.create(name, on_step=lambda msg: info(msg))
    success(f"Created app {app.name}.")
    hint("\nNext steps:")
    hint(f"  kuberoku deploy --app {app.name} --image <your-image> --port 80/tcp")
    hint(f"  kuberoku config:set --app {app.name} KEY=value")
    hint(f"  kuberoku apps:info --app {app.name}")


@apps.command()
@click.argument("name")
@click.option("--confirm", "confirm_name", default=None, help="Confirm by typing the app name.")
@click.pass_context
def destroy(ctx: click.Context, name: str, confirm_name: str | None) -> None:
    """Permanently destroy an app and all its resources."""
    if confirm_name != name and not click.confirm(f"Are you sure you want to destroy '{name}'?"):
        info("Aborted.")
        return
    factory = ctx.obj["factory"]
    factory.apps.destroy(name, on_step=lambda msg: info(msg))
    success(f"Destroyed app {name}.")


@apps.command("info")
@click.argument("name", required=False)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def info_cmd(ctx: click.Context, name: str | None, app_flag: str | None) -> None:
    """Show detailed app info."""
    app_name = name or resolve_app(app_flag)
    factory = ctx.obj["factory"]
    app = factory.apps.info(app_name)
    render_detail(
        app.name,
        {
            "Release": f"v{app.release_version}",
            "Created at": str(app.created_at),
            "Created by": app.created_by,
            "Process types": (
                ", ".join(app.process_types.keys()) if app.process_types else "(none)"
            ),
            "Maintenance": "on" if app.maintenance else "off",
        },
    )


@apps.command("status")
@click.argument("name", required=False)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def status_cmd(ctx: click.Context, name: str | None, app_flag: str | None) -> None:
    """Show aggregated app status (dashboard view)."""
    app_name = name or resolve_app(app_flag)
    factory = ctx.obj["factory"]
    st = factory.apps.status(app_name)
    _render_status(st)


def _render_status(st: AppStatus) -> None:
    """Render AppStatus in the dashboard format."""
    app = st.app

    click.echo(f"=== {app.name}")

    # Release
    if st.latest_release:
        r = st.latest_release
        click.echo(f"Release:      v{r.version} ({r.description})")
    else:
        click.echo("Release:      (none)")

    # Processes
    if st.dynos:
        click.echo("Processes:")
        # Group dynos by process type
        by_type: dict[str, list[Dyno]] = {}
        for d in st.dynos:
            by_type.setdefault(d.process_type, []).append(d)
        for ptype in sorted(by_type):
            dynos = by_type[ptype]
            running = sum(1 for d in dynos if d.state == "up")
            total = len(dynos)
            images = {d.image for d in dynos}
            image_str = ", ".join(sorted(images))
            port_info = ""
            if ptype in st.ports and st.ports[ptype]:
                port_strs = [f"{pm.port}/{pm.protocol.lower()}" for pm in st.ports[ptype]]
                port_info = f"  (ports: {', '.join(port_strs)})"
            click.echo(f"  {ptype:<12}{running}/{total} running   {image_str}{port_info}")
    else:
        click.echo("Processes:    (none)")

    # Config
    click.echo(f"Config:       {st.config_count} vars ({st.secret_count} secrets)")

    # Addons
    if st.addons:
        addon_strs = [f"{a.instance_name} ({a.status})" for a in st.addons]
        click.echo(f"Addons:       {', '.join(addon_strs)}")
    else:
        click.echo("Addons:       (none)")

    # Domains
    if st.domains:
        domain_strs = []
        for dom in st.domains:
            tls_str = " (TLS)" if dom.tls else ""
            domain_strs.append(f"{dom.hostname}{tls_str}")
        click.echo(f"Domains:      {', '.join(domain_strs)}")
    else:
        click.echo("Domains:      (none)")

    # Maintenance
    click.echo(f"Maintenance:  {'on' if app.maintenance else 'off'}")


@apps.command()
@click.argument("old_name")
@click.argument("new_name")
@click.pass_context
def rename(ctx: click.Context, old_name: str, new_name: str) -> None:
    """Rename an app."""
    factory = ctx.obj["factory"]
    app = factory.apps.rename(old_name, new_name, on_step=lambda msg: info(msg))
    success(f"Renamed to {app.name}.")


# ── link subgroup ────────────────────────────────────────────────


@apps.group("link", cls=_ColonGroup, invoke_without_command=True)
@click.pass_context
def link_group(ctx: click.Context) -> None:
    """Manage project directory links."""
    if ctx.invoked_subcommand is None:
        link = resolve_project_link()
        if link:
            msg = f"Linked to {link.app}"
            if link.cluster:
                msg += f" (cluster: {link.cluster})"
            info(msg)
        else:
            info("No project link found. Use 'apps:link:add <name>' to link.")


@link_group.command("add")
@click.argument("name")
@click.option("--as", "alias", default=None, help="Alias (e.g. staging, prod).")
@click.option("--cluster", default=None, help="Cluster for this alias.")
def link_add(name: str, alias: str | None, cluster: str | None) -> None:
    """Link current directory to an app."""
    path = write_project_file(app=name, alias=alias, cluster=cluster)
    if alias:
        success(f"Linked '{alias}' to {name} (wrote {path.name}).")
    else:
        success(f"Linked to {name} (wrote {path.name}).")


@link_group.command("remove")
@click.option("--as", "alias", default=None, help="Remove only this alias.")
def link_remove(alias: str | None) -> None:
    """Remove .kuberoku file (or a single alias)."""
    if alias:
        removed = remove_alias(alias)
        if removed:
            success(f"Removed alias '{alias}'.")
        else:
            info(f"Alias '{alias}' not found.")
    else:
        removed = remove_project_file()
        if removed:
            success("Unlinked (removed .kuberoku).")
        else:
            info("No .kuberoku file found.")


# ── maintenance subgroup ────────────────────────────────────────


@apps.group("maintenance", cls=_ColonGroup, invoke_without_command=True)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def maintenance_group(ctx: click.Context, app_flag: str | None) -> None:
    """Show or toggle maintenance mode."""
    ctx.ensure_object(dict)
    ctx.obj["_maint_app"] = app_flag

    if ctx.invoked_subcommand is not None:
        return

    # Default: show status
    app_name = resolve_app(app_flag)
    factory = ctx.obj["factory"]
    maint = factory.apps.maintenance_status(app_name)

    if maint.enabled:
        scope = "app-wide" if maint.app_wide else f"services: {', '.join(maint.services)}"
        since_str = maint.since.isoformat() if maint.since else "unknown"
        info(f"Maintenance: ON ({scope}, since {since_str})")
        if maint.saved_replicas:
            info(f"Saved replicas: {maint.saved_replicas}")
    else:
        info("Maintenance: OFF")


@maintenance_group.command("on")
@click.argument("name", required=False)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def maintenance_on(ctx: click.Context, name: str | None, app_flag: str | None) -> None:
    """Enable maintenance mode (scale all processes to 0)."""
    parent_app = ctx.obj.get("_maint_app")
    app_name = resolve_app(name or app_flag or parent_app)
    factory = ctx.obj["factory"]
    factory.apps.maintenance_on(app_name, on_step=lambda msg: info(msg))
    success(f"Maintenance mode enabled for {app_name}.")


@maintenance_group.command("off")
@click.argument("name", required=False)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def maintenance_off(ctx: click.Context, name: str | None, app_flag: str | None) -> None:
    """Disable maintenance mode (restore saved replicas)."""
    parent_app = ctx.obj.get("_maint_app")
    app_name = resolve_app(name or app_flag or parent_app)
    factory = ctx.obj["factory"]
    factory.apps.maintenance_off(app_name, on_step=lambda msg: info(msg))
    success(f"Maintenance mode disabled for {app_name}.")
