import builtins
from collections.abc import Sequence
from typing import Optional, Union
from uuid import UUID

from radicli import Arg
from wasabi import msg

from prodigy_teams_pam_sdk.models import ActionDetail, ActionSummary, RecipeDetail

from ..cli import cli
from ..errors import BrokerError, CLIError, HTTPXErrors, ProdigyTeamsError
from ..messages import Messages
from ..query import delete_job, resolve_action, resolve_recipe, start_job, stop_job
from ..ui import (
    print_args_table,
    print_info_table,
    print_logs,
    print_mutation_result,
    print_recipes_help,
    print_table_with_select,
)
from ._recipe_subcommand import (
    create_from_recipe,
    create_from_recipe_file,
    request_recipes,
)
from ._state import get_auth_state, get_saved_settings

# Module-level holder for recipe schema passed from create() to start().
_pending_recipe_schema: RecipeDetail | None = None


@cli.subcommand_with_extra(
    "actions",
    "create",
    exists_ok=Arg("--exists-ok", help=Messages.exists_ok),
    no_start=Arg("--no-start", help=Messages.no_start),
    no_wait=Arg("--no-wait", help=Messages.no_wait.format(noun="action")),
    _show_help=Arg("--help", "-h", help=Messages.help),
)
def create(
    exists_ok: bool = False,
    no_start: bool = False,
    no_wait: bool = False,
    _show_help: bool = False,
    _extra: builtins.list[str] = [],
) -> UUID | None:
    """
    Create a new action. The available action recipes are fetched from your
    cluster and are added as dynamic subcommands. You can see more details
    and available arguments by calling the subcommand with --help, e.g. create
    [name] --help
    """
    auth = get_auth_state()
    settings = get_saved_settings()
    is_file_mode = settings.recipes_file is not None
    try:
        action_schemas = request_recipes(auth=auth, is_action=True)
    except ProdigyTeamsError as e:
        raise CLIError(Messages.E009, e)
    except HTTPXErrors as e:
        raise CLIError(Messages.E009, e)
    if not _extra:
        print_recipes_help(action_schemas, "Create a new action", "actions create")
        return None
    args = [*_extra]
    name = args.pop(0)
    if name not in action_schemas:
        opts = f"Available: {', '.join(action_schemas.keys())}"
        raise CLIError(Messages.E010.format(noun="action", name=name), opts)
    if is_file_mode:
        schema = action_schemas[name]
        job_spec = create_from_recipe_file(
            schema, args, command="actions", show_help=_show_help, job_type="action"
        )
        msg.good(Messages.T002.format(noun="action", name=job_spec.name))
        print_args_table(job_spec.plan.args or {}, job_spec.plan.cli_names or {})
        if not no_start:
            response = auth.broker_client.jobs.start_job(job_spec)
            if response.cluster_change is not None:
                msg.good(Messages.T005.format(noun="action", name=job_spec.job_id))
            elif response.cluster_error is not None:
                raise CLIError(
                    Messages.E025.format(noun="action", name=job_spec.job_id),
                    response.cluster_error,
                )
        return job_spec.job_id
    schema = resolve_recipe(name, broker_id=auth.broker_id)
    action_id, plan = create_from_recipe(
        schema, args, command="actions", show_help=_show_help, exists_ok=exists_ok
    )
    msg.good(Messages.T002.format(noun="action", name=action_id))
    print_args_table(plan.args, schema.form_schema.cli_names)
    if not no_start:
        global _pending_recipe_schema
        _pending_recipe_schema = schema
        try:
            start(action_id, no_wait=no_wait)
        except Exception:
            auth.client.action.delete(id=action_id)
            raise
        finally:
            _pending_recipe_schema = None
    return action_id


@cli.subcommand(
    "actions",
    "list",
    # fmt: off
    select=Arg(
        "--select",
        help=Messages.select.format(
            opts=[*builtins.list(ActionSummary.model_fields), "state"]
        ),
    ),
    as_json=Arg("--json", help=Messages.as_json),
    # fmt: on
)
def list(
    select: builtins.list[str] = ["id", "name", "state", "project_name"],
    as_json: bool = False,
) -> Sequence[ActionSummary]:
    """
    List the actions on the cluster. By default, this includes their ID, name
    and current state, e.g. created or completed
    """
    auth = get_auth_state()
    items = builtins.list(auth.client.action.all(page_size=100))
    if "state" in select and items:
        job_ids = [item.id for item in items]
        try:
            statuses = auth.broker_client.jobs.get_batch_status(job_ids).statuses
        except (BrokerError, *HTTPXErrors):
            statuses = {}
        enriched = []
        for item in items:
            d = item.model_dump(include=set(s for s in select if s != "state"))
            status = statuses.get(str(item.id))
            d["state"] = status.state.value if status else "unknown"
            enriched.append(d)
        print_table_with_select(enriched, select=select, as_json=as_json)
    else:
        print_table_with_select(items, select=select, as_json=as_json)
    return items


@cli.subcommand(
    "actions",
    "info",
    name_or_id=Arg(help=Messages.name_or_id.format(noun="action")),
    project_id=Arg(help=Messages.project_id.format(noun="action")),
    cluster_id=Arg(help=Messages.cluster_id.format(noun="action")),
    select=Arg(
        "--select",
        help=Messages.select.format(opts=builtins.list(ActionDetail.model_fields)),
    ),
    as_json=Arg("--json", help=Messages.as_json),
)
def info(
    name_or_id: Union[str, UUID],
    cluster_id: Optional[UUID] = None,
    project_id: Optional[UUID] = None,
    select: Optional[builtins.list[str]] = None,
    as_json: bool = False,
) -> ActionDetail:
    """Print information about an action on the cluster"""
    auth = get_auth_state()
    res = resolve_action(name_or_id, broker_id=cluster_id, project_id=project_id)
    try:
        status = auth.broker_client.jobs.get_status(res.id)
        msg.text(f"State: {status.state}", icon="info")
    except (BrokerError, *HTTPXErrors):
        pass
    print_info_table(res, exclude=["plan"], as_json=as_json, select=select)
    return res


@cli.subcommand(
    "actions",
    "logs",
    name_or_id=Arg(help=Messages.name_or_id_optional.format(noun="action")),
    project_id=Arg(help=Messages.project_id.format(noun="action")),
    cluster_id=Arg(help=Messages.cluster_id.format(noun="action")),
    as_json=Arg("--json", help=Messages.as_json),
    show_errors=Arg(
        "--errors", help="Show structured error information instead of full logs"
    ),
    query=Arg("--query", help="Filter log lines matching this text (requires Loki)"),
)
def logs(
    name_or_id: Optional[Union[str, UUID]] = None,
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
    as_json: bool = False,
    show_errors: bool = False,
    query: Optional[str] = None,
) -> str | None:
    """Get logs for an action on the cluster"""
    job = resolve_action(name_or_id, broker_id=cluster_id, project_id=project_id)
    auth = get_auth_state()
    if job.last_execution_id is None:
        raise CLIError(Messages.E051.format(noun="action", name=job.name, id=job.id))
    if show_errors:
        try:
            resp = auth.broker_client.jobs.errors(job_id=job.last_execution_id)
        except BrokerError as e:
            raise CLIError(Messages.E011.format(noun="action"), e)
        if as_json:
            print(resp.model_dump_json(indent=2))
        else:
            msg.text(f"State: {resp.state}", icon="info")
            if resp.errors:
                for err in resp.errors:
                    msg.fail(err.message)
                    if err.traceback:
                        print(err.traceback)
                        print()
            elif resp.tail:
                msg.warn("No structured errors found. Last 50 lines:")
                print(resp.tail)
            else:
                msg.good("No errors found")
        return None
    try:
        text = auth.broker_client.jobs.logs(job_id=job.last_execution_id, query=query)
    except BrokerError as e:
        raise CLIError(Messages.E011.format(noun="action"), e)
    print_logs(text, as_json=as_json)
    return text


@cli.subcommand(
    "actions",
    "start",
    name_or_id=Arg(help=Messages.name_or_id_optional.format(noun="action")),
    project_id=Arg(help=Messages.project_id.format(noun="action")),
    cluster_id=Arg(help=Messages.cluster_id.format(noun="action")),
    worker_class=Arg(
        "--worker-class",
        help=Messages.recipe_worker_class.format(noun="action"),
    ),
    no_wait=Arg("--no-wait", help=Messages.no_wait.format(noun="action")),
    as_json=Arg("--json", help=Messages.as_json),
)
def start(
    name_or_id: Optional[Union[str, UUID]] = None,
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
    worker_class: Optional[str] = None,
    no_wait: bool = False,
    as_json: bool = False,
) -> UUID:
    """Start an action on the cluster"""
    job = resolve_action(name_or_id, broker_id=cluster_id, project_id=project_id)
    auth = get_auth_state()
    start_job(
        job,
        worker_class,
        auth,
        recipe_schema=_pending_recipe_schema,
        wait=not no_wait,
        quiet=as_json,
    )
    if as_json:
        print_mutation_result(
            {"id": str(job.id), "status": "started"}, "", as_json=True
        )
    return job.id


@cli.subcommand(
    "actions",
    "stop",
    name_or_id=Arg(help=Messages.name_or_id_optional.format(noun="action")),
    project_id=Arg(help=Messages.project_id.format(noun="action")),
    cluster_id=Arg(help=Messages.cluster_id.format(noun="action")),
    as_json=Arg("--json", help=Messages.as_json),
)
def stop(
    name_or_id: Optional[Union[str, UUID]] = None,
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
    as_json: bool = False,
) -> UUID:
    """Stop an action on the cluster"""
    job = resolve_action(name_or_id, broker_id=cluster_id, project_id=project_id)
    auth = get_auth_state()
    stop_job(job, auth, quiet=as_json)
    if as_json:
        print_mutation_result(
            {"id": str(job.id), "status": "stopped"}, "", as_json=True
        )
    return job.id


@cli.subcommand(
    "actions",
    "delete",
    name_or_id=Arg(help=Messages.name_or_id.format(noun="action")),
    project_id=Arg(help=Messages.project_id.format(noun="action")),
    cluster_id=Arg(help=Messages.cluster_id.format(noun="action")),
    as_json=Arg("--json", help=Messages.as_json),
)
def delete(
    name_or_id: Union[str, UUID],
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
    as_json: bool = False,
) -> UUID:
    """Delete an Action by name or ID"""
    job = resolve_action(name_or_id, broker_id=cluster_id, project_id=project_id)
    auth = get_auth_state()
    delete_job(job, auth, quiet=as_json)
    if as_json:
        print_mutation_result({"id": str(job.id), "deleted": True}, "", as_json=True)
    return job.id
