"""CLI commands for ps — list, scale, restart, stop, type, set, commands."""

from __future__ import annotations

import click

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


@click.group("ps", invoke_without_command=True)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def ps(ctx: click.Context, app_flag: str | None) -> None:
    """List all dynos."""
    ctx.ensure_object(dict)
    ctx.obj["_ps_app"] = app_flag

    if ctx.invoked_subcommand is not None:
        return

    app_name = resolve_app(app_flag)
    factory = ctx.obj["factory"]
    dynos = factory.ps.list_dynos(app_name)

    if not dynos:
        info("No dynos running.")
        return

    render_table(
        dynos,
        columns=["name", "state", "image", "started_at"],
    )


@ps.command("scale")
@click.argument("specs", nargs=-1, required=True)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def scale_cmd(ctx: click.Context, specs: tuple[str, ...], app_flag: str | None) -> None:
    """Scale process types (TYPE=N ...)."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    scaling: dict[str, int] = {}
    for spec in specs:
        if "=" not in spec:
            raise click.BadParameter(
                f"Invalid format: '{spec}'. Expected TYPE=N.",
                param_hint="'SPECS'",
            )
        ptype, count_str = spec.split("=", 1)
        try:
            count = int(count_str)
        except ValueError:
            raise click.BadParameter(
                f"Invalid count: '{count_str}'. Must be an integer.",
                param_hint="'SPECS'",
            ) from None
        scaling[ptype] = count

    result = factory.ps.scale(app_name, scaling, on_step=lambda msg: info(msg))
    for ptype, pf in result.items():
        success(f"{ptype}: {pf.replicas}")


@ps.command("restart")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--type", "process_type", default=None, help="Restart only this process type.")
@click.option("--dyno", default=None, help="Restart specific dyno (e.g., web.1).")
@click.pass_context
def restart_cmd(
    ctx: click.Context,
    app_flag: str | None,
    process_type: str | None,
    dyno: str | None,
) -> None:
    """Restart dynos."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    count = factory.ps.restart(
        app_name,
        process_type=process_type,
        dyno=dyno,
        on_step=lambda msg: info(msg),
    )
    success(f"Restarted {count} dyno(s).")


@ps.command("stop")
@click.argument("process_type")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def stop_cmd(ctx: click.Context, process_type: str, app_flag: str | None) -> None:
    """Stop a process type (scale to 0)."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    factory.ps.stop(app_name, process_type, on_step=lambda msg: info(msg))
    success(f"Stopped {process_type}.")


@ps.command("type")
@click.argument("process_type", required=False, default=None)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--cpu", default=None, help="CPU request/limit (e.g., '250m' or '250m/500m').")
@click.option(
    "--memory",
    default=None,
    help="Memory request/limit (e.g., '256Mi' or '256Mi/512Mi').",
)
@click.pass_context
def type_cmd(
    ctx: click.Context,
    process_type: str | None,
    app_flag: str | None,
    cpu: str | None,
    memory: str | None,
) -> None:
    """Show or change dyno resources."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    if cpu is not None or memory is not None:
        if process_type is None:
            raise click.UsageError("Process type required when setting resources.")
        factory.ps.set_type(
            app_name,
            process_type,
            cpu=cpu,
            memory=memory,
            on_step=lambda msg: info(msg),
        )
        success(f"Updated resources for {process_type}.")
        return

    result = factory.ps.get_type(app_name, process_type)
    if not result:
        info("No resources configured.")
        return

    for ptype, res in sorted(result.items()):
        requests = res.get("requests", {})
        limits = res.get("limits", {})
        parts = [f"{ptype}:"]
        if "cpu" in requests:
            cpu_str = requests["cpu"]
            if "cpu" in limits:
                cpu_str += f"/{limits['cpu']}"
            parts.append(f"cpu={cpu_str}")
        if "memory" in requests:
            mem_str = requests["memory"]
            if "memory" in limits:
                mem_str += f"/{limits['memory']}"
            parts.append(f"memory={mem_str}")
        if len(parts) == 1:
            parts.append("(no resources set)")
        click.echo(" ".join(parts))


@ps.command("set")
@click.argument("specs", nargs=-1, required=True)
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.option("--no-restart", "no_restart", is_flag=True, help="Don't trigger rolling restart.")
@click.pass_context
def set_cmd(
    ctx: click.Context,
    specs: tuple[str, ...],
    app_flag: str | None,
    no_restart: bool,
) -> None:
    """Set process commands (TYPE=CMD ...)."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    commands: dict[str, str] = {}
    for spec in specs:
        if "=" not in spec:
            raise click.BadParameter(
                f"Invalid format: '{spec}'. Expected TYPE=CMD.",
                param_hint="'SPECS'",
            )
        ptype, cmd = spec.split("=", 1)
        commands[ptype] = cmd

    factory.ps.set_commands(
        app_name, commands, no_restart=no_restart, on_step=lambda msg: info(msg)
    )
    for ptype, cmd in commands.items():
        success(f"{ptype}: {cmd}")


@ps.command("commands")
@click.option("--app", "-a", "app_flag", default=None, help="Target app.")
@click.pass_context
def commands_cmd(ctx: click.Context, app_flag: str | None) -> None:
    """Show commands per process type."""
    parent_app = ctx.obj.get("_ps_app")
    app_name = resolve_app(app_flag or parent_app)
    factory = ctx.obj["factory"]

    formation = factory.ps.get_commands(app_name)
    if not formation:
        info("No process types configured.")
        return

    for ptype, pf in sorted(formation.items()):
        cmd = pf.command or "(default)"
        source = f" [{pf.command_source}]" if pf.command else ""
        click.echo(f"{ptype}: {cmd}{source}")
