from __future__ import annotations

import json
import sys
from typing import Any

import typer

from simplicity_cli.errors import CliError
from simplicity_cli.models import JSONDict

SUMMARY_FIELDS = ("task_id", "form_id", "document_id", "download_url", "download_path")
_SPINNER_FRAMES = "|/-\\"
_TERMINAL_STATUSES = {"completed", "failed"}


class OutputReporter:
    def __init__(self, json_output: bool) -> None:
        self._json_output = json_output
        self._phase_index = 0
        self._supports_live_updates = (not json_output) and sys.stdout.isatty()
        self._live_task_id: str | None = None
        self._live_line_len = 0
        self._spinner_index = 0

    def phase(self, message: str) -> None:
        if not self._json_output:
            self._flush_live_line()
            self._phase_index += 1
            typer.echo(f"[{self._phase_index}] {message}")

    def detail(self, message: str) -> None:
        if not self._json_output:
            self._flush_live_line()
            typer.echo(f"    {message}")

    def task_update(
        self,
        *,
        label: str,
        task_id: str,
        status: str,
        progress: int | None,
        elapsed_seconds: float,
    ) -> None:
        if self._json_output:
            return

        if not self._supports_live_updates:
            progress_display = ""
            if progress is not None:
                progress_display = f", {progress}%"
            typer.echo(
                f"    {label}: {status}{progress_display} (task_id={task_id}, elapsed={int(elapsed_seconds)}s)"
            )
            return

        if self._live_task_id is not None and self._live_task_id != task_id:
            self._flush_live_line()

        self._live_task_id = task_id
        line = (
            f"\r    {label}: {self._format_progress_indicator(progress)} "
            f"{status:<12} elapsed {int(elapsed_seconds)}s"
        )
        padding = max(0, self._live_line_len - len(line))
        typer.echo(f"{line}{' ' * padding}", nl=False)
        self._live_line_len = len(line)

        if status in _TERMINAL_STATUSES:
            self._flush_live_line()

    def emit_success(self, command: str, payload: JSONDict) -> None:
        result = self._normalize_payload(command=command, ok=True, payload=payload, error_payload=None)
        if self._json_output:
            typer.echo(json.dumps(result, ensure_ascii=True))
            return

        self._flush_live_line()
        typer.echo("")
        self._print_human_summary(result)

    def emit_error(self, command: str, error: CliError) -> None:
        details = error.details if isinstance(error.details, dict) else {}
        error_payload = {
            "code": error.code,
            "message": error.message,
            "details": error.details,
        }
        payload = {
            "status": "error",
            "task_id": details.get("task_id"),
            "task": details.get("task"),
            "form_id": details.get("form_id"),
            "document_id": details.get("document_id"),
            "download_url": details.get("download_url"),
            "download_path": details.get("download_path"),
        }
        result = self._normalize_payload(
            command=command,
            ok=False,
            payload=payload,
            error_payload=error_payload,
        )
        if self._json_output:
            typer.echo(json.dumps(result, ensure_ascii=True))
            return

        self._flush_live_line()
        typer.echo(f"error: {error.message}")
        if error.code:
            typer.echo(f"code: {error.code}")
        for field in SUMMARY_FIELDS:
            value = result.get(field)
            if value:
                typer.echo(f"{field}: {value}")

    def _normalize_payload(
        self,
        *,
        command: str,
        ok: bool,
        payload: JSONDict,
        error_payload: JSONDict | None,
    ) -> JSONDict:
        return {
            "command": command,
            "ok": ok,
            "status": payload.get("status"),
            "task_id": payload.get("task_id"),
            "task": payload.get("task"),
            "form_id": payload.get("form_id"),
            "document_id": payload.get("document_id"),
            "download_url": payload.get("download_url"),
            "download_path": payload.get("download_path"),
            "error": error_payload,
        }

    @staticmethod
    def _print_human_summary(result: JSONDict) -> None:
        status = result.get("status")
        if isinstance(status, str) and status:
            typer.echo(f"status: {status}")

        for field in SUMMARY_FIELDS:
            value = result.get(field)
            if value:
                typer.echo(f"{field}: {value}")

    def _format_progress_indicator(self, progress: int | None) -> str:
        if progress is None:
            frame = _SPINNER_FRAMES[self._spinner_index % len(_SPINNER_FRAMES)]
            self._spinner_index += 1
            return f"[{frame}]"

        clamped = max(0, min(100, progress))
        width = 24
        filled = round((clamped / 100) * width)
        bar = "#" * filled + "-" * (width - filled)
        return f"[{bar}] {clamped:>3d}%"

    def _flush_live_line(self) -> None:
        if not self._supports_live_updates:
            return
        if self._live_task_id is None:
            return
        typer.echo("")
        self._live_task_id = None
        self._live_line_len = 0
