from __future__ import annotations

import csv
import json
from dataclasses import asdict, is_dataclass
from html import escape
from pathlib import Path
from typing import Any

from app.tools.local_stresstest.scenarios import scenario_description


def _svg_pipeline(title: str, steps: list[str]) -> str:
    box_w = 150
    box_h = 56
    gap = 34
    margin_x = 34
    margin_y = 64
    width = margin_x * 2 + len(steps) * box_w + (len(steps) - 1) * gap
    height = 170
    title_x = width // 2
    parts = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}" role="img" aria-label="{escape(title)}">',
        "<defs>",
        '<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">',
        '<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>',
        "</marker>",
        "</defs>",
        '<rect width="100%" height="100%" rx="10" fill="#f8fafc"/>',
        f'<text x="{title_x}" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="700" fill="#0f172a">{escape(title)}</text>',
    ]
    y = margin_y
    for idx, step in enumerate(steps):
        x = margin_x + idx * (box_w + gap)
        parts.append(f'<rect x="{x}" y="{y}" width="{box_w}" height="{box_h}" rx="7" fill="#ffffff" stroke="#94a3b8"/>')
        words = step.split()
        if len(words) > 2:
            mid = (len(words) + 1) // 2
            lines = [" ".join(words[:mid]), " ".join(words[mid:])]
        else:
            lines = [step]
        text_y = y + 28 - (len(lines) - 1) * 9
        for line in lines:
            parts.append(
                f'<text x="{x + box_w / 2:.1f}" y="{text_y}" text-anchor="middle" '
                f'font-family="Arial, sans-serif" font-size="13" fill="#0f172a">{escape(line)}</text>'
            )
            text_y += 18
        if idx < len(steps) - 1:
            x1 = x + box_w + 6
            x2 = x + box_w + gap - 8
            y_mid = y + box_h / 2
            parts.append(
                f'<line x1="{x1}" y1="{y_mid:.1f}" x2="{x2}" y2="{y_mid:.1f}" '
                'stroke="#2563eb" stroke-width="2.2" marker-end="url(#arrow)"/>'
            )
    parts.append("</svg>")
    return "\n".join(parts) + "\n"


STT_MERMAID = "\n".join(
    [
        "flowchart LR",
        "  A[WAV base] --> B[Variacao de audio]",
        "  B --> C[VAD proxy RMS]",
        "  C --> D[Sofya STT HTTP]",
        "  D --> E[Normalizar transcricao]",
        "  E --> F[Prefixo WER termos]",
        "  F --> G[Linha no CSV e Markdown]",
    ]
)

TTS_MERMAID = "\n".join(
    [
        "flowchart LR",
        "  A[Texto de teste] --> B[xAI websocket TTS]",
        "  B --> C[PCM audio]",
        "  C --> D[Artefato WAV]",
        "  C --> E[Duracao, RMS e clipping]",
        "  E --> F[Linha no CSV e Markdown]",
    ]
)

E2E_MERMAID = "\n".join(
    [
        "sequenceDiagram",
        "  participant Runner",
        "  participant Bridge",
        "  participant LiveKit",
        "  participant Agent",
        "  participant Sofya",
        "  participant XAI",
        "  Runner->>Bridge: start WS + frames PCM",
        "  Bridge->>LiveKit: publica audio do cliente",
        "  LiveKit->>Agent: track de audio",
        "  Agent->>Sofya: reconhece fala",
        "  Sofya-->>Agent: transcricao",
        "  Agent->>Agent: resposta remote_ws_fake",
        "  Agent->>XAI: sintetiza resposta",
        "  XAI-->>Agent: audio PCM",
        "  Agent-->>LiveKit: track de audio",
        "  LiveKit-->>Bridge: audio do agente",
        "  Bridge-->>Runner: frames PCM + stop",
    ]
)

STT_SVG = _svg_pipeline(
    "Fluxo STT",
    ["WAV base", "Variacao audio", "VAD proxy", "Sofya STT", "Prefixo WER", "CSV Markdown"],
)

TTS_SVG = _svg_pipeline(
    "Fluxo TTS",
    ["Texto teste", "xAI TTS", "PCM audio", "WAV gerado", "Metricas audio", "CSV Markdown"],
)

E2E_SVG = _svg_pipeline(
    "Fluxo E2E Bridge LiveKit",
    ["Runner", "Bridge", "LiveKit", "Agent", "Sofya STT", "xAI TTS", "Bridge"],
)


def _json_default(value: Any) -> Any:
    if isinstance(value, Path):
        return str(value)
    if is_dataclass(value):
        return asdict(value)
    return str(value)


def write_json(path: Path | str, payload: Any) -> Path:
    out = Path(path)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(payload, ensure_ascii=False, indent=2, default=_json_default), encoding="utf-8")
    return out


def write_csv(path: Path | str, rows: list[dict[str, Any]]) -> Path:
    out = Path(path)
    out.parent.mkdir(parents=True, exist_ok=True)
    fieldnames: list[str] = []
    for row in rows:
        for key in row:
            if key not in fieldnames:
                fieldnames.append(key)
    with out.open("w", encoding="utf-8", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        for row in rows:
            writer.writerow({key: row.get(key, "") for key in fieldnames})
    return out


def write_mermaid_files(report_dir: Path | str) -> dict[str, str]:
    base_dir = Path(report_dir) / "diagrams"
    base_dir.mkdir(parents=True, exist_ok=True)
    files = {
        "stt_mermaid": base_dir / "stt_flow.mmd",
        "tts_mermaid": base_dir / "tts_flow.mmd",
        "e2e_mermaid": base_dir / "e2e_flow.mmd",
        "stt_svg": base_dir / "stt_flow.svg",
        "tts_svg": base_dir / "tts_flow.svg",
        "e2e_svg": base_dir / "e2e_flow.svg",
    }
    files["stt_mermaid"].write_text(STT_MERMAID + "\n", encoding="utf-8")
    files["tts_mermaid"].write_text(TTS_MERMAID + "\n", encoding="utf-8")
    files["e2e_mermaid"].write_text(E2E_MERMAID + "\n", encoding="utf-8")
    files["stt_svg"].write_text(STT_SVG, encoding="utf-8")
    files["tts_svg"].write_text(TTS_SVG, encoding="utf-8")
    files["e2e_svg"].write_text(E2E_SVG, encoding="utf-8")
    return {key: value.as_posix() for key, value in files.items()}


def _status(value: bool) -> str:
    return "APROVADO" if value else "FALHOU"


def _human_bool(value: Any) -> str:
    if value is True:
        return "sim"
    if value is False:
        return "nao"
    return str(value)


def _translate_error(value: Any) -> str:
    text = str(value or "")
    translations = {
        "missing expected ready/stop/audio/timeline signal": (
            "faltou algum sinal esperado: ready, stop, audio ou timeline"
        ),
    }
    return translations.get(text, text)


def _md_table(rows: list[dict[str, Any]], columns: list[str | tuple[str, str]]) -> str:
    if not rows:
        return "_Nenhuma linha._"
    keys = [item[0] if isinstance(item, tuple) else item for item in columns]
    labels = [item[1] if isinstance(item, tuple) else item for item in columns]
    header = "| " + " | ".join(labels) + " |"
    sep = "| " + " | ".join("---" for _ in labels) + " |"
    body = []
    for row in rows:
        values = []
        for key in keys:
            value = row.get(key, "")
            if key == "description" and not value:
                value = scenario_description(str(row.get("scenario") or ""))
            if key == "passed":
                value = _status(value is True)
            elif isinstance(value, bool):
                value = _human_bool(value)
            elif key == "error":
                value = _translate_error(value)
            text = str(value).replace("\n", " ").replace("|", "\\|")
            values.append(text)
        body.append("| " + " | ".join(values) + " |")
    return "\n".join([header, sep, *body])


def render_markdown_report(summary: dict[str, Any]) -> str:
    stt_rows = summary.get("stt_results", [])
    tts_rows = summary.get("tts_results", [])
    e2e_rows = summary.get("e2e_results", [])
    startup_rows = list((summary.get("startup_checks") or {}).values())
    synthetic = bool(summary.get("baseline", {}).get("synthetic"))
    overall_passed = bool(summary.get("passed"))

    lines = [
        "# Relatorio do Teste Local de Estresse",
        "",
        "## Resumo Executivo",
        "",
        f"- Status geral: **{_status(overall_passed)}**",
        f"- Inicio: `{summary.get('started_at', '')}`",
        f"- Duracao: `{summary.get('duration_ms', 0)} ms`",
        f"- Audio base: `{summary.get('baseline', {}).get('path', '')}`",
        f"- Texto esperado: `{summary.get('expected_text', '')}`",
        f"- Texto sintetizado: `{summary.get('synthesis_text') or summary.get('expected_text', '')}`",
        f"- Chamadas STT: `{summary.get('stt_total_calls', len(stt_rows))}`",
        f"- Prefixo monitorado: `{summary.get('stt_prefix_text', '')}`",
    ]
    vad_proxy = summary.get("vad_proxy") or {}
    if vad_proxy:
        lines.append(
            "- VAD proxy: "
            f"`threshold={vad_proxy.get('threshold_dbfs')} dBFS`, "
            f"`prefix_padding={vad_proxy.get('prefix_padding_ms')} ms`, "
            f"`min_speech={vad_proxy.get('min_speech_ms')} ms`"
        )
    diagnostics = summary.get("diagnostics") or {}
    if diagnostics:
        lines.append(
            "- Diagnostico audio/VAD: "
            f"`STT_DUMP_DIR={diagnostics.get('stt_dump_dir', '')}`, "
            f"`FLOW_LOG_VAD_DECISIONS={diagnostics.get('flow_log_vad_decisions', '')}`, "
            f"`FLOW_LOG_VAD_ACTIVITY={diagnostics.get('flow_log_vad_activity', '')}`"
        )
    if synthetic:
        lines.extend(
            [
                "",
                "> Aviso: esta execucao usou um audio base sintetico gerado pelo xAI TTS. Quando houver uma gravacao humana, substitua com `STRESS_AUDIO`.",
            ]
        )
    if startup_rows:
        lines.extend(
            [
                "",
                "## Prontidao Local",
                "",
                _md_table(
                    startup_rows,
                    [
                        ("service", "servico"),
                        ("url", "url"),
                        ("status", "status"),
                        ("attempts", "tentativas"),
                        ("detail", "detalhe"),
                    ],
                ),
            ]
        )

    lines.extend(
        [
            "",
            "## Cenarios STT",
            "",
            _md_table(
                stt_rows,
                [
                    ("scenario", "cenario"),
                    ("description", "descricao"),
                    ("passed", "status"),
                    ("prefix_ok", "prefixo_ok"),
                    ("vad_proxy_low_start_risk", "risco_inicio"),
                    ("vad_proxy_first_voice_ms", "1a_voz_ms"),
                    ("vad_proxy_unrecovered_prefix_ms", "prefixo_descoberto_ms"),
                    ("wer", "WER"),
                    ("duration_ms", "duracao_ms"),
                    ("rms_dbfs", "rms_dbfs"),
                    ("transcript", "transcricao"),
                    ("missing_terms", "termos_ausentes"),
                    ("error", "erro"),
                ],
            ),
            "",
            "## Cenarios TTS",
            "",
            _md_table(
                tts_rows,
                [
                    ("scenario", "cenario"),
                    ("description", "descricao"),
                    ("passed", "status"),
                    ("duration_ms", "duracao_ms"),
                    ("latency_ms", "latencia_ms"),
                    ("rms_dbfs", "rms_dbfs"),
                    ("clipping_ratio", "taxa_clipping"),
                    ("audio_path", "audio"),
                    ("error", "erro"),
                ],
            ),
            "",
            "## Cenarios E2E Bridge/LiveKit",
            "",
            _md_table(
                e2e_rows,
                [
                    ("scenario", "cenario"),
                    ("description", "descricao"),
                    ("passed", "status"),
                    ("ready_received", "recebeu_ready"),
                    ("stop_received", "recebeu_stop"),
                    ("non_silent_frames", "frames_com_audio"),
                    ("timeline_transcript", "transcricao_timeline"),
                    ("audio_path", "audio"),
                    ("error", "erro"),
                ],
            ),
            "",
            "## Fluxo STT",
            "",
            "Imagem estatica para preview no VSCode:",
            "",
            "![Fluxo STT](diagrams/stt_flow.svg)",
            "",
            "Versao textual:",
            "",
            "1. O runner carrega o WAV base.",
            "2. Gera uma variacao de audio para o cenario.",
            "3. Calcula um VAD proxy por energia RMS para estimar risco de inicio fraco.",
            "4. Envia o audio para o Sofya STT HTTP.",
            "5. Normaliza a transcricao retornada.",
            "6. Verifica prefixo esperado, WER e termos criticos.",
            "7. Escreve a linha correspondente no CSV e no Markdown.",
            "",
            f"Arquivo Mermaid separado: `{summary.get('artifacts', {}).get('stt_mermaid', '')}`",
            "",
            "Codigo Mermaid:",
            "",
            "```mermaid",
            STT_MERMAID,
            "```",
            "",
            "## Fluxo TTS",
            "",
            "Imagem estatica para preview no VSCode:",
            "",
            "![Fluxo TTS](diagrams/tts_flow.svg)",
            "",
            "Versao textual:",
            "",
            "1. O runner envia um texto de teste para o xAI TTS.",
            "2. O xAI retorna audio PCM pelo websocket.",
            "3. O runner salva o audio como WAV.",
            "4. Calcula duracao, RMS e clipping.",
            "5. Escreve a linha correspondente no CSV e no Markdown.",
            "",
            f"Arquivo Mermaid separado: `{summary.get('artifacts', {}).get('tts_mermaid', '')}`",
            "",
            "Codigo Mermaid:",
            "",
            "```mermaid",
            TTS_MERMAID,
            "```",
            "",
            "## Fluxo E2E",
            "",
            "Imagem estatica para preview no VSCode:",
            "",
            "![Fluxo E2E](diagrams/e2e_flow.svg)",
            "",
            "Versao textual:",
            "",
            "1. O runner abre websocket no Bridge e envia `start`.",
            "2. O Bridge publica o audio do cliente no LiveKit.",
            "3. O Agent recebe o audio e chama Sofya STT.",
            "4. O backend fake gera a resposta de negocio.",
            "5. O Agent chama xAI TTS para sintetizar a resposta.",
            "6. O audio volta pelo LiveKit para o Bridge.",
            "7. O Bridge devolve audio PCM e, ao final ideal, envia `stop`.",
            "",
            f"Arquivo Mermaid separado: `{summary.get('artifacts', {}).get('e2e_mermaid', '')}`",
            "",
            "Codigo Mermaid:",
            "",
            "```mermaid",
            E2E_MERMAID,
            "```",
            "",
            "## Artefatos",
            "",
            f"- Summary JSON: `{summary.get('artifacts', {}).get('summary_json', '')}`",
            f"- CSV STT: `{summary.get('artifacts', {}).get('stt_csv', '')}`",
            f"- CSV TTS: `{summary.get('artifacts', {}).get('tts_csv', '')}`",
            f"- CSV E2E: `{summary.get('artifacts', {}).get('e2e_csv', '')}`",
            f"- Recorte da timeline: `{summary.get('artifacts', {}).get('timeline_excerpt', '')}`",
            f"- Mermaid STT: `{summary.get('artifacts', {}).get('stt_mermaid', '')}`",
            f"- Mermaid TTS: `{summary.get('artifacts', {}).get('tts_mermaid', '')}`",
            f"- Mermaid E2E: `{summary.get('artifacts', {}).get('e2e_mermaid', '')}`",
            f"- SVG STT: `{summary.get('artifacts', {}).get('stt_svg', '')}`",
            f"- SVG TTS: `{summary.get('artifacts', {}).get('tts_svg', '')}`",
            f"- SVG E2E: `{summary.get('artifacts', {}).get('e2e_svg', '')}`",
        ]
    )
    return "\n".join(lines) + "\n"


def write_markdown_report(path: Path | str, summary: dict[str, Any]) -> Path:
    out = Path(path)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(render_markdown_report(summary), encoding="utf-8")
    return out
