# /apps/aroflo_connector_app/zones/tasks/cli.py
from __future__ import annotations

import json
from typing import Any, Dict, List, Optional, Callable

import click


DEFAULT_WHERE = "and|createdutc|>|2001-01-01"


def _echo(result: Any) -> None:
    if isinstance(result, (dict, list)):
        click.echo(json.dumps(result, indent=2, ensure_ascii=False))
    else:
        click.echo(str(result))


def _available_ops(zone: Any) -> List[str]:
    ops = getattr(zone, "operations", [])
    return sorted([o.code for o in ops])


def _run(zone: Any, op_code: str, params: Dict[str, Any]) -> Any:
    available = set(_available_ops(zone))
    if op_code not in available:
        click.echo(f"❌ Operación '{op_code}' no existe en zona '{zone.code}'.")
        click.echo("Operaciones disponibles:")
        for c in _available_ops(zone):
            click.echo(f"  - {c}")
        raise SystemExit(2)
    return zone.execute(op_code, params=params)


def _add_optional(
    params: Dict[str, Any],
    *,
    pagesize: Optional[int],
    order: Optional[str],
) -> Dict[str, Any]:
    """
    AroFlo usa pageSize como parámetro de entrada para tamaño de página.
    """
    if pagesize is not None:
        params["pageSize"] = pagesize

    if order:
        params["order"] = order

    return params


def _common_list_options(fn: Callable[..., Any]) -> Callable[..., Any]:
    """
    Decorador para opciones comunes de list/join:
    - page, where, order, pageSize (AroFlo) y raw
    """
    fn = click.option("--page", default=1, type=int, show_default=True)(fn)
    fn = click.option("--where", default=DEFAULT_WHERE, show_default=True, help="Cláusula WHERE estilo AroFlo.")(fn)
    fn = click.option("--order", default=None, show_default=True, help="ORDER estilo AroFlo. Ej: createdutc|desc")(fn)

    fn = click.option(
        "--pagesize",
        type=int,
        default=None,
        show_default=True,
        help="Cantidad de registros por página (AroFlo pageSize). Ej: 5, 20, 50.",
    )(fn)

    fn = click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")(fn)
    return fn


def _require_confirm_if_needed(*, confirm: bool, dry_run: bool, op_label: str) -> None:
    if dry_run:
        return
    if not confirm:
        raise SystemExit(f"❌ Falta --confirm. Operación de escritura cancelada ({op_label}).")


def register_cli(root: click.Group, zone: Any) -> None:
    @root.group(name=zone.code)
    def tasks_group():
        """Operaciones de la zona tasks."""

    # -------------------------
    # BASE
    # -------------------------
    @tasks_group.command("list")
    @_common_list_options
    def list_cmd(
        page: int,
        where: str,
        order: Optional[str],
        pagesize: Optional[int],
        raw: bool,
    ):
        params = _add_optional(
            {"page": page, "where": where, "raw": raw},
            pagesize=pagesize,
            order=order,
        )
        _echo(_run(zone, "list_tasks", params))

    @tasks_group.command("get")
    @click.option("--taskid", required=True, help="TaskID codificado (AroFlo).")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    def get_cmd(taskid: str, raw: bool):
        _echo(_run(zone, "get_task", {"taskid": taskid, "raw": raw}))

    @tasks_group.command("due-range")
    @click.option("--daterangetype", required=True, default="DueDate", show_default=True)
    @click.option("--fromdate", required=True, help="YYYY-MM-DD")
    @click.option("--todate", required=True, help="YYYY-MM-DD")
    @click.option("--where", default=None, help="WHERE adicional (opcional).")
    @click.option("--order", default=None, show_default=True, help="ORDER estilo AroFlo. Ej: createdutc|desc")
    @click.option("--page", default=1, type=int, show_default=True)
    @click.option("--pagesize", type=int, default=None, show_default=True, help="Cantidad de registros por página (AroFlo pageSize).")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    def due_range_cmd(
        daterangetype: str,
        fromdate: str,
        todate: str,
        where: Optional[str],
        order: Optional[str],
        page: int,
        pagesize: Optional[int],
        raw: bool,
    ):
        params: Dict[str, Any] = {
            "daterangetype": daterangetype,
            "fromdate": fromdate,
            "todate": todate,
            "where": where,
            "order": order,
            "page": page,
            "raw": raw,
        }
        params = _add_optional(params, pagesize=pagesize, order=order)
        _echo(_run(zone, "get_tasks_due_for_date_range", params))

    # -------------------------
    # MUTATIONS (write)
    # -------------------------
    @tasks_group.command("create")
    @click.option("--orgid", required=False, default=None, help="OrgID (si aplica para tu tenant).")
    @click.option("--clientid", required=True, help="ClientID (AroFlo).")
    @click.option("--tasktypeid", required=True, help="TaskTypeID (AroFlo).")
    @click.option("--summary", required=True, help="Resumen / título de la task.")
    @click.option("--duedate", required=False, default=None, help="YYYY/MM/DD o YYYY-MM-DD (según AroFlo).")
    @click.option("--description", required=False, default=None, help="Descripción (opcional).")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo CREAR una task (escritura).")
    def create_cmd(
        orgid: Optional[str],
        clientid: str,
        tasktypeid: str,
        summary: str,
        duedate: Optional[str],
        description: Optional[str],
        raw: bool,
        dry_run: bool,
        confirm: bool,
    ):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="create")

        params: Dict[str, Any] = {
            "clientid": clientid,
            "tasktypeid": tasktypeid,
            "summary": summary,
            "raw": raw,
            "dry_run": dry_run,
        }
        if orgid:
            params["orgid"] = orgid
        if duedate:
            params["duedate"] = duedate
        if description:
            params["description"] = description

        _echo(_run(zone, "create_task", params))

    @tasks_group.command("update")
    @click.option("--taskid", required=True, help="TaskID (AroFlo).")
    @click.option("--status", required=False, default=None, help="Nuevo status (quote, notstarted, inprogress, etc.).")
    @click.option("--summary", required=False, default=None, help="Nuevo resumen / título.")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo ACTUALIZAR una task (escritura).")
    def update_cmd(taskid: str, status: Optional[str], summary: Optional[str], raw: bool, dry_run: bool, confirm: bool):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="update")

        params: Dict[str, Any] = {"taskid": taskid, "raw": raw, "dry_run": dry_run}
        if status is not None:
            params["status"] = status
        if summary is not None:
            params["summary"] = summary

        _echo(_run(zone, "update_task", params))

    @tasks_group.command("add-note")
    @click.option("--taskid", required=True, help="TaskID destino (AroFlo).")
    @click.option("--content", required=True, help="Contenido de la nota (HTML o texto).")
    @click.option("--filter", "filter_", default=None, help="Filtro (ej: internal admin only, internal only).")
    @click.option("--sticky", is_flag=True, help="Marca la nota como sticky.")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo INSERTAR una nota (escritura).")
    def add_note_cmd(taskid: str, content: str, filter_: Optional[str], sticky: bool, raw: bool, dry_run: bool, confirm: bool):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="add-note")

        params: Dict[str, Any] = {
            "taskid": taskid,
            "notes": [{"content": content, "filter": filter_, "sticky": sticky}],
            "raw": raw,
            "dry_run": dry_run,
        }
        _echo(_run(zone, "insert_task_notes", params))

    @tasks_group.command("add-material")
    @click.option("--taskid", required=True, help="TaskID destino (AroFlo).")
    @click.option("--item", required=True, help="Nombre del item/material.")
    @click.option("--partnumber", default=None, help="Partnumber (opcional).")
    @click.option("--cost", type=float, default=None, help="Costo unitario (opcional).")
    @click.option("--sell", type=float, default=None, help="Precio venta unitario (opcional).")
    @click.option("--dateused", default=None, help="Fecha usada (YYYY/MM/DD).")
    @click.option("--quantity", type=float, default=None, help="Cantidad (opcional).")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo INSERTAR material (escritura).")
    def add_material_cmd(
        taskid: str,
        item: str,
        partnumber: Optional[str],
        cost: Optional[float],
        sell: Optional[float],
        dateused: Optional[str],
        quantity: Optional[float],
        raw: bool,
        dry_run: bool,
        confirm: bool,
    ):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="add-material")

        material: Dict[str, Any] = {"item": item}
        if partnumber:
            material["partnumber"] = partnumber
        if cost is not None:
            material["cost"] = cost
        if sell is not None:
            material["sell"] = sell
        if dateused:
            material["dateused"] = dateused
        if quantity is not None:
            material["quantity"] = quantity

        params: Dict[str, Any] = {"taskid": taskid, "materials": [material], "raw": raw, "dry_run": dry_run}
        _echo(_run(zone, "insert_task_adhoc_materials", params))

    @tasks_group.command("mark-processed")
    @click.option("--taskid", multiple=True, required=True, help="Repetible. Ej: --taskid X --taskid Y")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo marcar linkprocessed=true (escritura).")
    def mark_processed_cmd(taskid: tuple[str, ...], raw: bool, dry_run: bool, confirm: bool):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="mark-processed")
        params = {"taskids": list(taskid), "raw": raw, "dry_run": dry_run}
        _echo(_run(zone, "mark_task_linkprocessed", params))

    @tasks_group.command("update-substatus")
    @click.option("--taskid", required=True, help="TaskID destino (AroFlo).")
    @click.option("--status", required=True, help="Status (ej: pending).")
    @click.option("--substatusid", required=True, help="SubstatusID.")
    @click.option("--raw", is_flag=True, help="Devuelve respuesta cruda + meta debug.")
    @click.option("--dry-run", is_flag=True, help="No ejecuta POST; muestra preview (postxml/params).")
    @click.option("--confirm", is_flag=True, help="Confirmo que deseo actualizar substatus (escritura).")
    def update_substatus_cmd(taskid: str, status: str, substatusid: str, raw: bool, dry_run: bool, confirm: bool):
        _require_confirm_if_needed(confirm=confirm, dry_run=dry_run, op_label="update-substatus")
        params = {"taskid": taskid, "status": status, "substatusid": substatusid, "raw": raw, "dry_run": dry_run}
        _echo(_run(zone, "update_task_substatus", params))

    # -------------------------
    # JOINS (registración compacta)
    # -------------------------
    def add_join_command(cmd_name: str, op_code: str, help_text: str) -> None:
        @tasks_group.command(cmd_name, help=help_text)
        @_common_list_options
        def _cmd(
            page: int,
            where: str,
            order: Optional[str],
            pagesize: Optional[int],
            raw: bool,
        ):
            params = _add_optional({"page": page, "where": where, "raw": raw}, pagesize=pagesize, order=order)
            _echo(_run(zone, op_code, params))

    add_join_command("assets", "get_tasks_with_assets", "Tasks + join=assets")
    add_join_command("assignedhistory", "get_tasks_with_assignedhistory", "Tasks + join=assignedhistory")
    add_join_command("customfields", "get_tasks_with_customfields", "Tasks + join=customfields")
    add_join_command("documentsandphotos", "get_tasks_with_documentsandphotos", "Tasks + join=documentsandphotos")
    add_join_command("expense", "get_tasks_with_expense", "Tasks + join=expense")
    add_join_command("labour", "get_tasks_with_labour", "Tasks + join=labour")
    add_join_command("material", "get_tasks_with_material", "Tasks + join=material")
    add_join_command("notes", "get_tasks_with_notes", "Tasks + join=notes")
    add_join_command("purchaseorders", "get_tasks_with_purchaseorders", "Tasks + join=purchaseorders")
    add_join_command("quote", "get_tasks_with_quote", "Tasks + join=quote")
    add_join_command("tasktotals", "get_tasks_with_tasktotals", "Tasks + join=tasktotals")
    add_join_command("location", "get_tasks_with_location", "Tasks + join=location")
    add_join_command("locationcustomfields", "get_tasks_with_locationcustomfields", "Tasks + join=locationcustomfields")
    add_join_command("project", "get_tasks_with_project", "Tasks + join=project")
    add_join_command("salesperson", "get_tasks_with_salesperson", "Tasks + join=salesperson")
    add_join_command("substatus", "get_tasks_with_substatus", "Tasks + join=substatus")
