"""
Nightly Report Renderers.

Pluggable renderers over the `report_pipeline` data model.  v1 ships the HTML
email body + the Excel attachment; PDF and an inline chart image are v1.1
(each needs a new dependency).  Keeping renderers separate from the compute
core means a future report wizard just toggles metrics/renderers — the data
model is unchanged.

Email-client constraints: the HTML uses a table layout with **inline styles
only** (no <style> blocks, no external CSS, no fl/grid), which is the reliable
common denominator across Outlook / Gmail / Apple Mail.
"""

from __future__ import annotations

from html import escape

from backend.services.report_pipeline import ProjectNightReport, LocationNightReport


# Colours: louder-than-baseline reads as a concern (red), quieter as fine (green).
_RED = "#b00020"
_GREEN = "#1a7f37"
_GREY = "#888888"


def _fmt_value(v) -> str:
    return f"{v:.1f}" if isinstance(v, (int, float)) else "—"


def _fmt_delta(v) -> str:
    """Signed delta with colour; positive (louder) = red, negative (quieter) = green."""
    if not isinstance(v, (int, float)):
        return f'<span style="color:{_GREY}">—</span>'
    if v > 0:
        return f'<span style="color:{_RED}">+{v:.1f}</span>'
    if v < 0:
        return f'<span style="color:{_GREEN}">{v:.1f}</span>'
    return f'<span style="color:{_GREY}">0.0</span>'


def _location_table(loc: LocationNightReport) -> str:
    """One location block: heading + Metric × (window: Last / Base / Δ) table."""
    th = ('padding:5px 9px;border:1px solid #ccc;background:#f2f2f2;'
          'font:bold 12px Arial,sans-serif;text-align:center')
    sub = ('padding:4px 8px;border:1px solid #ccc;background:#fafafa;'
           'font:11px Arial,sans-serif;text-align:center;color:#555')
    td = 'padding:4px 9px;border:1px solid #ccc;font:12px Arial,sans-serif;text-align:center'
    td_l = 'padding:4px 9px;border:1px solid #ccc;font:bold 12px Arial,sans-serif;text-align:left'

    # Top header: blank label cell + each window spanning Last/Base/Δ
    top = f'<th rowspan="2" style="{th}">Metric (dBA)</th>'
    for w in loc.windows:
        top += f'<th colspan="3" style="{th}">{escape(w.label)}</th>'
    sub_row = ''.join(
        f'<th style="{sub}">Last</th><th style="{sub}">Base</th><th style="{sub}">&Delta;</th>'
        for _ in loc.windows
    )

    body = ''
    for m in loc.metrics:
        cells = ''
        for w in loc.windows:
            cp = loc.table[w.key][m.key]
            cells += (f'<td style="{td}">{_fmt_value(cp.last_night)}</td>'
                      f'<td style="{td}">{_fmt_value(cp.baseline)}</td>'
                      f'<td style="{td}">{_fmt_delta(cp.delta)}</td>')
        body += f'<tr><td style="{td_l}">{escape(m.label)}</td>{cells}</tr>'

    meta = (f'{loc.night_interval_count} intervals'
            + (f' · baseline = {loc.baseline_nights_used} night(s)'
               if loc.baseline_nights_used else ' · no baseline yet'))
    notes = ''
    if loc.notes:
        notes = ('<div style="font:11px Arial,sans-serif;color:#b00020;margin:2px 0 0">'
                 + '<br>'.join(escape(n) for n in loc.notes) + '</div>')

    return (
        f'<h3 style="font:bold 15px Arial,sans-serif;margin:18px 0 4px">{escape(loc.location_name)}</h3>'
        f'<div style="font:11px Arial,sans-serif;color:#666;margin:0 0 6px">{escape(meta)}</div>'
        f'<table style="border-collapse:collapse;border:1px solid #ccc">'
        f'<thead><tr>{top}</tr><tr>{sub_row}</tr></thead>'
        f'<tbody>{body}</tbody></table>{notes}'
    )


def render_html_summary(report: ProjectNightReport) -> str:
    """Render the full email-body HTML for a project's night report."""
    windows_desc = ", ".join(w.label for w in (report.locations[0].windows if report.locations else []))
    header = (
        f'<h2 style="font:bold 18px Arial,sans-serif;margin:0 0 2px">'
        f'{escape(report.project_name)} — Night Report</h2>'
        f'<div style="font:13px Arial,sans-serif;color:#444;margin:0 0 4px">'
        f'Night of {report.night_date:%a %m/%d/%y} &nbsp;·&nbsp; last night vs. baseline</div>'
        f'<div style="font:11px Arial,sans-serif;color:#888;margin:0 0 10px">'
        f'Windows: {escape(windows_desc)}. '
        f'&Delta; = last night minus baseline (<span style="color:{_RED}">+ louder</span>, '
        f'<span style="color:{_GREEN}">− quieter</span>). '
        f'LAmax = loudest interval; L-values are arithmetic averages; '
        f'baseline = typical night.</div>'
    )

    if not report.locations:
        body = ('<div style="font:13px Arial,sans-serif;color:#b00020">'
                'No sound locations found for this project.</div>')
    else:
        body = ''.join(_location_table(loc) for loc in report.locations)

    footer = ('<div style="font:10px Arial,sans-serif;color:#aaa;margin-top:18px">'
              'Automated report — Terra-View. Full interval data in the attached spreadsheet.</div>')

    return (f'<!DOCTYPE html><html><body style="margin:0;padding:16px;background:#fff">'
            f'{header}{body}{footer}</body></html>')


# ---------------------------------------------------------------------------
# Excel renderer (the email attachment) — one sheet per location:
# interval table + line chart + a Last/Baseline/Δ summary per window.
# Metric-driven, so it adapts to whatever metric set is configured.
# ---------------------------------------------------------------------------

def _safe_sheet_name(name: str) -> str:
    bad = set('[]:*?/\\')
    cleaned = "".join(c for c in (name or "Location") if c not in bad).strip()
    return (cleaned or "Location")[:31]


def render_excel(report: ProjectNightReport) -> bytes:
    """Render the night report as an .xlsx (bytes). One worksheet per location."""
    import io as _io
    import openpyxl
    from openpyxl.chart import LineChart, Reference
    from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
    from openpyxl.utils import get_column_letter

    wb = openpyxl.Workbook()
    wb.remove(wb.active)

    f_title = Font(name="Arial", bold=True, size=13)
    f_h = Font(name="Arial", bold=True, size=10)
    f_d = Font(name="Arial", size=10)
    f_note = Font(name="Arial", size=9, italic=True, color="888888")
    center = Alignment(horizontal="center", vertical="center")
    hdr_fill = PatternFill("solid", fgColor="F2F2F2")
    thin = Side(style="thin")
    box = Border(left=thin, right=thin, top=thin, bottom=thin)

    if not report.locations:
        ws = wb.create_sheet("No data")
        ws["A1"] = f"{report.project_name} — no sound locations"
        ws["A1"].font = f_title

    used_names: set = set()
    for loc in report.locations:
        sheet_name = _safe_sheet_name(loc.location_name)
        n, base = sheet_name, sheet_name
        i = 2
        while n in used_names:
            n = (base[:28] + f"_{i}"); i += 1
        used_names.add(n)
        ws = wb.create_sheet(n)
        metrics = loc.metrics

        ws["A1"] = f"{report.project_name} — Night Report"; ws["A1"].font = f_title
        ws["A2"] = loc.location_name; ws["A2"].font = f_h
        ws["A3"] = f"Night of {loc.night_date:%m/%d/%y}  ·  7PM–7AM"; ws["A3"].font = f_d

        # --- interval table ---
        hr = 5
        cols = ["Interval #", "Date", "Time"] + [m.label for m in metrics] + ["Comments"]
        for ci, label in enumerate(cols, 1):
            c = ws.cell(row=hr, column=ci, value=label)
            c.font = f_h; c.alignment = center; c.fill = hdr_fill; c.border = box
        r = hr + 1
        for idx, entry in enumerate(loc.interval_series, 1):
            ws.cell(row=r, column=1, value=idx).border = box
            dt = entry.get("dt")
            ws.cell(row=r, column=2, value=(dt.strftime("%m/%d/%y") if dt else "")).border = box
            ws.cell(row=r, column=3, value=entry.get("time", "")).border = box
            for mi, m in enumerate(metrics):
                v = entry.get(m.key)
                cc = ws.cell(row=r, column=4 + mi, value=(v if isinstance(v, (int, float)) else None))
                cc.border = box; cc.alignment = center
            ws.cell(row=r, column=4 + len(metrics), value="").border = box
            r += 1
        data_end = max(r - 1, hr + 1)

        ws.column_dimensions["A"].width = 9
        ws.column_dimensions["B"].width = 10
        ws.column_dimensions["C"].width = 8
        for mi in range(len(metrics)):
            ws.column_dimensions[get_column_letter(4 + mi)].width = 11
        ws.column_dimensions[get_column_letter(4 + len(metrics))].width = 22

        # --- chart ---
        if loc.interval_series and metrics:
            chart = LineChart()
            chart.title = f"{loc.location_name} — {loc.night_date:%m/%d/%y}"
            chart.y_axis.title = "dBA"; chart.x_axis.title = "Time"
            chart.height = 9; chart.width = 18
            data_ref = Reference(ws, min_col=4, max_col=3 + len(metrics), min_row=hr, max_row=data_end)
            cats = Reference(ws, min_col=3, min_row=hr + 1, max_row=data_end)
            chart.add_data(data_ref, titles_from_data=True)
            chart.set_categories(cats)
            ws.add_chart(chart, f"{get_column_letter(6 + len(metrics))}5")

        # --- summary: Metric × window (Last / Base / Δ) ---
        sr = data_end + 3
        ws.cell(row=sr, column=1, value="Summary — last night vs baseline").font = f_h
        sr += 1
        ws.cell(row=sr, column=1, value="Metric").font = f_h
        win_col = {}
        col = 2
        for w in loc.windows:
            c = ws.cell(row=sr, column=col, value=w.label); c.font = f_h; c.alignment = center
            ws.merge_cells(start_row=sr, start_column=col, end_row=sr, end_column=col + 2)
            win_col[w.key] = col
            col += 3
        sr += 1
        for w in loc.windows:
            b = win_col[w.key]
            for j, lbl in enumerate(["Last", "Base", "Δ"]):
                cc = ws.cell(row=sr, column=b + j, value=lbl); cc.font = f_h; cc.alignment = center
        sr += 1
        for m in metrics:
            ws.cell(row=sr, column=1, value=m.label).font = f_d
            for w in loc.windows:
                cp = loc.table[w.key][m.key]
                b = win_col[w.key]
                ws.cell(row=sr, column=b + 0, value=cp.last_night).alignment = center
                ws.cell(row=sr, column=b + 1, value=cp.baseline).alignment = center
                ws.cell(row=sr, column=b + 2, value=cp.delta).alignment = center
            sr += 1
        if loc.notes:
            ws.cell(row=sr + 1, column=1, value="; ".join(loc.notes)).font = f_note

    out = _io.BytesIO()
    wb.save(out)
    return out.getvalue()
