# src/dattos_cli/commands/analyses.py
from __future__ import annotations

import time
from pathlib import Path
from typing import Optional

import typer

from dattos_cli.client import ApiError
from dattos_cli.main import get_client, handle_api_error
from dattos_cli.output import is_tty, print_json, validate_code, validate_date
from dattos_cli.table import print_record, print_table


def _fmt_active(v) -> str:
    return "yes" if v else "—"


ANALYSES_COLUMNS = [
    ("ID",     "id",       None),
    ("Code",   "code",     None),
    ("Name",   "name",     None),
    ("Active", "isActive", _fmt_active),
]

_POLL_INTERVAL = 5  # seconds between status polls

TERMINAL_STATUSES = {"Loaded", "Error", "NoLoad", "Inactive", "InvalidPreparation"}

ALLOWED_EXTENSIONS = {".csv", ".edi", ".ofx", ".pdf", ".rem", ".ret", ".txt", ".xlsx", ".xls", ".xlsb", ".xml"}

app = typer.Typer(help="Manage analyses.", no_args_is_help=True)


@app.command("list")
def list_analyses(
    search: Optional[str] = typer.Option(None, "--search", "-s", help="Filter by code or name"),
    show_inactive: bool = typer.Option(False, "--inactive", help="Include inactive analyses"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
    page: int = typer.Option(0, "--page", help="Page number (0-indexed)"),
    page_size: int = typer.Option(50, "--page-size", help="Results per page"),
    no_cache: bool = typer.Option(False, "--no-cache", help="Bypass cache, fetch fresh data."),
    raw: bool = typer.Option(False, "--raw", help="Output raw JSON."),
):
    """List analyses."""
    client = get_client(folder_id)
    params = {
        "pageInfo.page": page,
        "pageInfo.pageSize": page_size,
        "showInactives": show_inactive,
    }
    if search:
        params["codeOrName"] = search
    try:
        resp = client.get("/api/Analysis", params=params, cache=not no_cache, cache_ttl=300)
        # /api/Analysis returns {"data": [...], "totalItems": N} — lowercase "data" unlike /api/User
        data = resp.get("data", resp) if isinstance(resp, dict) else resp
        if raw or not is_tty():
            print_json(data)
        else:
            print_table(data, ANALYSES_COLUMNS, label="análises")
    except ApiError as e:
        handle_api_error(e)


@app.command("get")
def get_analysis(
    analysis_id: int = typer.Argument(help="Analysis ID"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
    raw: bool = typer.Option(False, "--raw", help="Output raw JSON."),
):
    """Get an analysis by ID."""
    client = get_client(folder_id)
    try:
        result = client.get(f"/api/Analysis/{analysis_id}")
        if raw or not is_tty():
            print_json(result)
        else:
            print_record(result)
    except ApiError as e:
        handle_api_error(e)


@app.command("get-by-code")
def get_by_code(
    code: str = typer.Argument(help="Analysis code"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
    raw: bool = typer.Option(False, "--raw", help="Output raw JSON."),
):
    """Get an analysis by its unique code."""
    client = get_client(folder_id)
    try:
        result = client.get(f"/api/Analysis/GetByCode/{validate_code(code)}")
        if raw or not is_tty():
            print_json(result)
        else:
            print_record(result)
    except ApiError as e:
        handle_api_error(e)


@app.command("datasources")
def get_datasources(
    analysis_id: int = typer.Argument(help="Analysis ID"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
):
    """List data sources for an analysis (includes connector info)."""
    client = get_client(folder_id)
    try:
        result = client.get(f"/api/Analysis/{analysis_id}/Datasources")
        print_json(result)
    except ApiError as e:
        handle_api_error(e)


@app.command("execution-status")
def execution_status(
    analysis_id: int = typer.Argument(help="Analysis ID"),
    date: str = typer.Option(..., "--date", "-d", help="Reference date (YYYY-MM-DD)"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
):
    """Get the execution status for an analysis at a reference date."""
    client = get_client(folder_id)
    try:
        result = client.get(f"/api/Analysis/{analysis_id}/Execution/Status/{validate_date(date)}")
        print_json(result)
    except ApiError as e:
        handle_api_error(e)


@app.command("start")
def start_execution(
    analysis_id: int = typer.Argument(help="Analysis ID"),
    date: str = typer.Option(..., "--date", "-d", help="Reference date (YYYY-MM-DD)"),
    incremental: bool = typer.Option(False, "--incremental", help="Use incremental importing (adds data, preserves existing)"),
    wait: bool = typer.Option(False, "--wait", "-w", help="Wait for execution to complete (polls status)"),
    file: Optional[str] = typer.Option(None, "--file", help="File to upload for import-based execution"),
    folder_id: Optional[str] = typer.Option(None, "--folder-id", help="Folder ID to scope the query"),
):
    """Start an analysis execution for a reference date.

    If --file is provided, uploads the file first then starts execution with it.
    If --wait is provided, polls execution-status until completion.
    """
    validate_date(date)
    client = get_client(folder_id)
    files_payload: list[dict] = []

    if file:
        file_path = Path(file)
        if not file_path.exists():
            from dattos_cli.output import print_error
            print_error("file_not_found", f"File not found: {file}", 1)
            raise typer.Exit(1)

        if file_path.suffix.lower() not in ALLOWED_EXTENSIONS:
            from dattos_cli.output import print_error
            print_error(
                "invalid_extension",
                f"Unsupported file extension: {file_path.suffix}. Allowed: {', '.join(sorted(ALLOWED_EXTENSIONS))}",
                1,
            )
            raise typer.Exit(1)

        # Step 1: Create ImportRequest
        try:
            import_req = client.post("/api/ImportRequest", json={
                "FileName": file_path.name,
                "FolderId": int(client.folder_id or 0),
            })
        except ApiError as e:
            handle_api_error(e)
            return
        req_id = import_req.get("Id") or import_req.get("id")

        # Step 2: Upload file content
        try:
            client.upload(f"/api/ImportRequest/{req_id}/Content", file_path.read_bytes())
        except ApiError as e:
            handle_api_error(e)
            return

        files_payload.append({"UploadFileId": req_id, "DataSourceIds": []})

    body = {
        "ReferenceDate": date,
        "IncrementalImporting": incremental,
        "Files": files_payload,
    }
    try:
        client.post(f"/api/Analysis/{analysis_id}/Execution/Start", json=body)
        print_json({"status": "started", "analysisId": analysis_id, "referenceDate": date})
    except ApiError as e:
        handle_api_error(e)
        return

    if not wait:
        return

    use_progress = is_tty()
    while True:
        time.sleep(_POLL_INTERVAL)
        try:
            status = client.get(f"/api/Analysis/{analysis_id}/Execution/Status/{date}")
        except ApiError as e:
            if use_progress:
                typer.echo()
            handle_api_error(e)
            return

        current = status.get("status", "Unknown")
        pct = status.get("completedPercent", 0)

        if use_progress:
            bar_width = 20
            filled = int(bar_width * pct / 100) if pct else 0
            bar = "\u2588" * filled + "\u2591" * (bar_width - filled)
            typer.echo(f"\r  {bar} {pct:>3}%  {current}", nl=False)
        else:
            print_json(status)

        if current in TERMINAL_STATUSES:
            if use_progress:
                typer.echo()
                typer.echo()
                typer.echo(f"  Done: {current}")
            if current == "Error":
                raise typer.Exit(1)
            return
