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

from radicli import Arg
from wasabi import msg

from ..cli import cli
from ..errors import BrokerError, CLIError, HTTPXErrors, ProdigyTeamsError
from ..messages import Messages
from ..prodigy_teams_pam_sdk.models import ActionDetail, ActionSummary, RecipeDetail
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_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:
    """
    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")
        sys.exit(0)
    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
        start(action_id, no_wait=no_wait)
        _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"], 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()
    res = auth.client.action.all(page=1, size=100)
    if res is None:
        raise CLIError("Could not retrieve actions list")
    items = res.items
    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.value}", 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")),
)
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,
) -> 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,
    )
    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")),
)
def stop(
    name_or_id: Optional[Union[str, UUID]] = None,
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
) -> 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)
    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")),
)
def delete(
    name_or_id: Union[str, UUID],
    project_id: Optional[UUID] = None,
    cluster_id: Optional[UUID] = None,
) -> 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)
    return job.id
