"""Output formatting: text, markdown, CSV, JSON."""

from __future__ import annotations

import contextlib
import csv
import io
import json
from pathlib import Path
from typing import Any

from sb_scout.models import AppConfig, EXPORT_FIELDS


# ─── Helpers ──────────────────────────────────────────────────────────────────


CURRENCY_SYMBOLS = {
    "EUR": "€",
    "USD": "$",
}


def money(v: float, currency: str = "EUR") -> str:
    symbol = CURRENCY_SYMBOLS.get(currency.upper(), currency.upper() + " ")
    return f"{symbol}{v:.0f}"


def fmt_tb(gb: int) -> str:
    return f"{gb / 1000:.2f} TB"


def markdown_escape(v: Any) -> str:
    return str(v).replace("|", "\\|").replace("\n", " ")


def row_tags(row: dict[str, Any]) -> str:
    tags: list[str] = []
    if row["ecc"]:
        tags.append("ECC")
    if row["has_inic"]:
        tags.append("iNIC")
    if row["has_gpu"]:
        tags.append("GPU")
    if row["has_ssd"] and row["has_hdd"]:
        tags.append("Mixed")
    elif row["has_ssd"]:
        tags.append("SSD-only")
    elif row["has_hdd"]:
        tags.append("HDD-only")
    return ", ".join(tags) if tags else "-"


# ─── Metadata ─────────────────────────────────────────────────────────────────


def report_metadata(
    config: AppConfig,
    feed_count: int,
    scored_count: int,
) -> list[tuple[str, str]]:
    m: list[tuple[str, str]] = [("Source", config.url)]
    if config.profiles:
        m.append(("Profiles", ", ".join(config.profiles)))
    m.append(("Currency", config.currency))
    m.append(("Pricing", "net / ex VAT"))
    if config.cache_dir:
        m.append(("Cache dir", str(config.cache_dir)))
    f = config.filters
    m.extend([
        ("Servers in feed", str(feed_count)),
        ("Servers scored", str(scored_count)),
        ("ECC only", str(f.ecc_only)),
        ("Price cap", money(f.price_cap, config.currency) if f.price_cap is not None else "none"),
        ("Minimum RAM", f"{f.min_ram_gb} GB"),
        ("Maximum RAM", f"{f.max_ram_gb} GB" if f.max_ram_gb is not None else "none"),
        ("Minimum storage", fmt_tb(f.min_storage_gb)),
        ("Maximum storage", fmt_tb(f.max_storage_gb) if f.max_storage_gb is not None else "none"),
        ("Minimum SSD/NVMe drives", str(f.disk.min_ssd_drives)),
        ("Minimum HDD drives", str(f.disk.min_hdd_drives)),
        ("SSD only", str(f.disk.ssd_only)),
        ("Datacenter filter", f.datacenter or "none"),
        ("Exclude datacenter", f.exclude_datacenter or "none"),
        ("CPU regex", f.cpu_regex_raw or "none"),
        ("Exclude CPU regex", f.exclude_cpu_regex_raw or "none"),
        ("Only GPU", str(f.only_gpu)),
        ("Price per thread cap",
         f"{f.price_per_thread_cap:.2f} {config.currency}" if f.price_per_thread_cap is not None else "none"),
        ("Sort by", config.sort_by),
        ("Bonuses",
         f"iNIC=+{config.bonuses.inic_bonus * 100:.0f}% "
         f"GPU=+{config.bonuses.gpu_bonus * 100:.0f}% "
         "(multiplicative if both)"),
        ("Weights",
         f"cpu_rank=compute {config.weights.cpu_rank_compute:.2f} / ram {config.weights.cpu_rank_ram:.2f}; "
         f"storage_rank=storage {config.weights.storage_rank_storage:.2f} / ram {config.weights.storage_rank_ram:.2f}; "
         f"overall=compute {config.weights.overall_compute:.2f} / ram {config.weights.overall_ram:.2f} / "
         f"storage {config.weights.overall_storage:.2f}"),
    ])
    return m


# ─── Text output ──────────────────────────────────────────────────────────────


def _text_section(title: str, key: str, ranked: list[dict[str, Any]], *, currency: str) -> str:
    lines = [f"\n{title}"]
    for i, r in enumerate(ranked, 1):
        lines.append(
            f"{i}. id={r['id']}  score={r[key] * 100:.2f}  price={money(r['price_amount'], currency)}  "
            f"cpu={r['cpu']}  threads={r['threads']}  ram={r['ram_gb']}GB  "
            f"storage={fmt_tb(r['storage_gb'])}  dc={r['datacenter']}  "
            f"bonus=x{r['bonus_multiplier']:.2f}  tags={row_tags(r)}"
        )
        lines.append(f"   drives: {r['drives']}")
    return "\n".join(lines)


def build_text_report(
    *,
    config: AppConfig,
    feed_count: int,
    rows: list[dict[str, Any]],
    sort_key: str,
    tops: dict[str, list[dict[str, Any]]],
) -> str:
    parts = ["Hetzner Serverbörse value rankings"]
    for label, val in report_metadata(config, feed_count, len(rows)):
        parts.append(f"{label}: {val}")
    parts.append(_text_section(f"Top by selected sort ({config.sort_by})", sort_key, tops["selected"], currency=config.currency))
    parts.append(_text_section("Top CPU value (RAM-weighted)", "score_cpu_rank", tops["cpu"], currency=config.currency))
    parts.append(_text_section("Top storage value (RAM-weighted)", "score_storage_rank", tops["storage"], currency=config.currency))
    parts.append(_text_section("Top overall value", "score_overall_rank", tops["overall"], currency=config.currency))
    return "\n".join(parts) + "\n"


# ─── Markdown output ──────────────────────────────────────────────────────────


def _md_section(title: str, key: str, ranked: list[dict[str, Any]], *, currency: str) -> str:
    lines = [
        f"\n## {title}\n",
        "| Rank | ID | Score | Price | CPU | Threads | RAM | Storage | DC | Bonus | Tags | Drives |",
        "| --- | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | ---: | --- | --- |",
    ]
    for i, r in enumerate(ranked, 1):
        lines.append(
            f"| {i} | {markdown_escape(r['id'])} | {r[key] * 100:.2f} | "
            f"{money(r['price_amount'], currency)} | {markdown_escape(r['cpu'])} | {r['threads']} | "
            f"{r['ram_gb']} GB | {fmt_tb(r['storage_gb'])} | {markdown_escape(r['datacenter'])} | "
            f"x{r['bonus_multiplier']:.2f} | {markdown_escape(row_tags(r))} | "
            f"{markdown_escape(r['drives'])} |"
        )
    return "\n".join(lines)


def build_markdown_report(
    *,
    config: AppConfig,
    feed_count: int,
    rows: list[dict[str, Any]],
    sort_key: str,
    tops: dict[str, list[dict[str, Any]]],
) -> str:
    parts = ["# Hetzner Serverbörse value rankings\n"]
    for label, val in report_metadata(config, feed_count, len(rows)):
        parts.append(f"- {label}: **{markdown_escape(val)}**")
    parts.append(_md_section(f"Top by selected sort ({config.sort_by})", sort_key, tops["selected"], currency=config.currency))
    parts.append(_md_section("Top CPU value (RAM-weighted)", "score_cpu_rank", tops["cpu"], currency=config.currency))
    parts.append(_md_section("Top storage value (RAM-weighted)", "score_storage_rank", tops["storage"], currency=config.currency))
    parts.append(_md_section("Top overall value", "score_overall_rank", tops["overall"], currency=config.currency))
    return "\n".join(parts) + "\n"


# ─── JSON summary ─────────────────────────────────────────────────────────────


def build_summary(
    *,
    config: AppConfig,
    feed_count: int,
    rows: list[dict[str, Any]],
    tops: dict[str, list[dict[str, Any]]],
    export_rows: list[dict[str, Any]],
) -> dict[str, Any]:
    return {
        "source": config.url,
        "profile": config.profile,
        "profiles": list(config.profiles),
        "currency": config.currency,
        "pricing": "net / ex VAT",
        "filters": {
            "ecc_only": config.filters.ecc_only,
            "price_cap": config.filters.price_cap,
            "min_ram_gb": config.filters.min_ram_gb,
            "max_ram_gb": config.filters.max_ram_gb,
            "min_storage_gb": config.filters.min_storage_gb,
            "max_storage_gb": config.filters.max_storage_gb,
            "min_ssd_drives": config.filters.disk.min_ssd_drives,
            "min_hdd_drives": config.filters.disk.min_hdd_drives,
            "ssd_only": config.filters.disk.ssd_only,
            "datacenter": config.filters.datacenter,
            "exclude_datacenter": config.filters.exclude_datacenter,
            "cpu_regex": config.filters.cpu_regex_raw,
            "exclude_cpu_regex": config.filters.exclude_cpu_regex_raw,
            "only_gpu": config.filters.only_gpu,
            "price_per_thread_cap": config.filters.price_per_thread_cap,
        },
        "bonuses": {
            "inic_bonus": config.bonuses.inic_bonus,
            "gpu_bonus": config.bonuses.gpu_bonus,
            "stacking": "multiplicative",
        },
        "weights": {
            "cpu_rank": {"compute": config.weights.cpu_rank_compute, "ram": config.weights.cpu_rank_ram},
            "storage_rank": {"storage": config.weights.storage_rank_storage, "ram": config.weights.storage_rank_ram},
            "overall": {
                "compute": config.weights.overall_compute,
                "ram": config.weights.overall_ram,
                "storage": config.weights.overall_storage,
            },
        },
        "counts": {
            "feed_servers": feed_count,
            "cache_dir": str(config.cache_dir) if config.cache_dir else None,
            "scored_servers": len(rows),
            "ecc_servers": sum(1 for r in rows if r["ecc"]),
            "inic_servers": sum(1 for r in rows if r["has_inic"]),
            "gpu_servers": sum(1 for r in rows if r["has_gpu"]),
            "ssd_only_servers": sum(1 for r in rows if r["has_ssd"] and not r["has_hdd"]),
            "mixed_servers": sum(1 for r in rows if r["has_ssd"] and r["has_hdd"]),
        },
        "sort_by": config.sort_by,
        "top": {
            "selected_sort": tops["selected"],
            "cpu_rank": tops["cpu"],
            "storage_rank": tops["storage"],
            "overall_rank": tops["overall"],
        },
        "rows": export_rows,
    }


# ─── File exports ─────────────────────────────────────────────────────────────


def write_csv(rows: list[dict[str, Any]], path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=EXPORT_FIELDS)
        w.writeheader()
        for r in rows:
            w.writerow({k: r.get(k) for k in EXPORT_FIELDS})


def write_json(data: Any, path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2) + "\n")


def write_exports(
    rows: list[dict[str, Any]],
    summary: dict[str, Any],
    out_dir: Path,
    *,
    csv_out: str | None = None,
    json_out: str | None = None,
) -> tuple[Path, Path]:
    out_dir.mkdir(parents=True, exist_ok=True)
    csv_path = Path(csv_out) if csv_out else out_dir / "sb_scout_rankings.csv"
    json_path = Path(json_out) if json_out else out_dir / "sb_scout_rankings.json"
    write_csv(rows, csv_path)
    write_json(summary, json_path)
    return csv_path, json_path
