from __future__ import annotations

import asyncio
import time
import uuid
from typing import Any, Awaitable, Callable

import typer
from pydantic import ValidationError

from flow_xhs.runtime.errors import InvalidPublishParameterError, PublishNoteException
from flow_xhs.platform.base import PlatformActionableError, RiskControlException
from flow_xhs.cli.output.formatter_json import format_json, format_jsonl
from flow_xhs.cli.output.formatter_table import format_table
from flow_xhs.cli.output.writer import write_output
from flow_xhs.contracts.command_outputs import build_error_output, build_success_output
from flow_xhs.cli.context import CliContext
from flow_xhs.errors import CliError, InternalError
from flow_xhs.exit_codes import ExitCode, map_error_code_to_exit_code


async def _with_timeout(coro: Awaitable[Any], timeout: int) -> Any:
    return await asyncio.wait_for(coro, timeout=timeout)


def _render_payload(payload: Any, output_format: str) -> str:
    if output_format == "json":
        return format_json(payload)
    if output_format == "jsonl":
        if isinstance(payload, list):
            return format_jsonl(payload)
        return format_jsonl([payload])
    if hasattr(payload, "model_dump"):
        payload = payload.model_dump(mode="json")
    if isinstance(payload, dict) and "data" in payload:
        return format_table(payload["data"])
    return format_table(payload)


def _normalize_error(exc: Exception) -> CliError:
    exc_details = getattr(exc, "details", None)
    if isinstance(exc, CliError):
        return exc
    if isinstance(exc, ValidationError):
        try:
            normalized_errors = exc.errors(include_context=False, include_input=False)
        except TypeError:
            normalized_errors = exc.errors()
        return CliError(code="INVALID_ARGS", message="参数校验失败", details={"errors": normalized_errors})
    if isinstance(exc, InvalidPublishParameterError):
        return CliError(code="INVALID_ARGS", message=str(exc), details=exc_details)
    if isinstance(exc, RiskControlException):
        return CliError(code="RISK_CONTROL_TRIGGERED", message=str(exc), details=exc_details)
    if isinstance(exc, PlatformActionableError):
        return CliError(code=getattr(exc, "code", "INTERNAL_ERROR"), message=str(exc), details=exc_details)
    if isinstance(exc, asyncio.TimeoutError):
        return CliError(code="TIMEOUT", message="命令执行超时")
    if isinstance(exc, PublishNoteException):
        return CliError(code="INTERNAL_ERROR", message=str(exc), details=exc_details)
    if "No available" in str(exc):
        return CliError(code="ACCOUNT_UNAVAILABLE", message=str(exc))
    internal = InternalError(message=f"未处理异常: {exc}")
    return CliError(code=internal.code, message=internal.message)


def run_sync_command(
    *,
    ctx: CliContext,
    command: str,
    func: Callable[[], Any],
    platform: str | None = None,
    account_uid: str | None = None,
) -> None:
    effective_platform = platform or ctx.platform_name
    trace_id = ctx.trace_id or uuid.uuid4().hex
    start = time.time()
    try:
        data = func()
        duration_ms = int((time.time() - start) * 1000)
        payload = build_success_output(
            command=command,
            data=data,
            trace_id=trace_id,
            duration_ms=duration_ms,
            platform=effective_platform,
            account_uid=account_uid,
        )
        write_output(_render_payload(payload, ctx.output_format), out_file=ctx.out_file)
        raise typer.Exit(code=ExitCode.SUCCESS.value)
    except typer.Exit:
        raise
    except Exception as exc:
        err = _normalize_error(exc)
        duration_ms = int((time.time() - start) * 1000)
        payload = build_error_output(
            command=command,
            code=err.code,
            message=err.message,
            details=err.details,
            trace_id=trace_id,
            duration_ms=duration_ms,
        )
        write_output(format_json(payload), out_file=ctx.out_file)
        raise typer.Exit(code=map_error_code_to_exit_code(err.code).value)


def run_async_command(
    *,
    ctx: CliContext,
    command: str,
    func: Callable[[], Awaitable[Any]],
    platform: str | None = None,
    account_uid: str | None = None,
) -> None:
    effective_platform = platform or ctx.platform_name
    trace_id = ctx.trace_id or uuid.uuid4().hex
    start = time.time()
    try:
        data = asyncio.run(_with_timeout(func(), timeout=ctx.timeout))
        duration_ms = int((time.time() - start) * 1000)
        payload = build_success_output(
            command=command,
            data=data,
            trace_id=trace_id,
            duration_ms=duration_ms,
            platform=effective_platform,
            account_uid=account_uid,
        )
        write_output(_render_payload(payload, ctx.output_format), out_file=ctx.out_file)
        raise typer.Exit(code=ExitCode.SUCCESS.value)
    except typer.Exit:
        raise
    except Exception as exc:
        err = _normalize_error(exc)
        duration_ms = int((time.time() - start) * 1000)
        payload = build_error_output(
            command=command,
            code=err.code,
            message=err.message,
            details=err.details,
            trace_id=trace_id,
            duration_ms=duration_ms,
        )
        write_output(format_json(payload), out_file=ctx.out_file)
        raise typer.Exit(code=map_error_code_to_exit_code(err.code).value)
