"""Daily report state, collection, PDF generation, and sending."""

import json
import logging
import re
import time
from datetime import datetime, timedelta
from pathlib import Path
from textwrap import wrap

from armorer.messaging.telegram import escape_markdown_v2

logger = logging.getLogger("ArmorerCore")


def _load_daily_report_state(handler) -> dict:
    if not handler._daily_report_state_file.exists():
        return {}
    try:
        with open(handler._daily_report_state_file, "r") as f:
            raw = json.load(f)
        return raw if isinstance(raw, dict) else {}
    except Exception:
        return {}


def _save_daily_report_state(handler, state: dict) -> None:
    try:
        with open(handler._daily_report_state_file, "w") as f:
            json.dump(state, f, indent=2)
    except Exception:
        pass


def _daily_report_config(handler) -> tuple[bool, str, int]:
    enabled_raw = (handler.config_manager.get_key("ARMORER_DAILY_REPORT_ENABLED") or "true").strip().lower()
    enabled = enabled_raw not in {"0", "false", "off", "no"}
    report_time = (handler.config_manager.get_key("ARMORER_DAILY_REPORT_TIME") or "09:00").strip()
    if not re.match(r"^[0-2][0-9]:[0-5][0-9]$", report_time):
        report_time = "09:00"
    window_raw = handler.config_manager.get_key("ARMORER_DAILY_REPORT_WINDOW_HOURS") or "24"
    try:
        window_hours = max(1, min(168, int(str(window_raw).strip())))
    except Exception:
        window_hours = 24
    return enabled, report_time, window_hours


def _collect_daily_report_stats(handler, end_time: datetime, window_hours: int) -> tuple[datetime, dict[str, dict], dict[str, list[str]]]:
    from armorer.core.openclaw_setup import _list_apps_with_openclaw_fallback
    start_time = end_time - timedelta(hours=window_hours)
    history_dir = handler.config_dir / "conversation_history"
    by_agent: dict[str, dict] = {}
    snippets_by_agent: dict[str, list[str]] = {}
    installed_app_names: set[str] = set()

    for app in _list_apps_with_openclaw_fallback(handler):
        installed_app_names.add(app.name)
        by_agent[app.name] = {
            "events": 0,
            "runtime_events": 0,
            "suspicious": 0,
            "redactions": 0,
            "reasons": {},
        }
        snippets_by_agent[app.name] = []

    if history_dir.exists():
        for f in history_dir.glob("agent:*.jsonl"):
            app_name = f.stem.split("agent:", 1)[-1]
            if app_name not in installed_app_names:
                continue
            try:
                lines = f.read_text().splitlines()
            except Exception:
                continue
            for line in lines:
                try:
                    row = json.loads(line)
                except Exception:
                    continue
                ts = row.get("ts")
                if not isinstance(ts, (int, float)):
                    continue
                dt = datetime.fromtimestamp(float(ts))
                if dt < start_time or dt > end_time:
                    continue

                info = by_agent[app_name]
                info["events"] += 1
                if str(row.get("role")) == "agent_runtime":
                    info["runtime_events"] += 1
                text = str(row.get("text") or "")
                if "[REDACTED_" in text:
                    info["redactions"] += 1
                if bool(row.get("suspicious")):
                    info["suspicious"] += 1
                    for reason in row.get("reasons", []) or []:
                        rs = str(reason)
                        info["reasons"][rs] = info["reasons"].get(rs, 0) + 1
                snippet = " ".join(str(row.get("text") or "").split())
                if snippet:
                    snippet = snippet[:220]
                    arr = snippets_by_agent.setdefault(app_name, [])
                    if len(arr) < 12:
                        arr.append(snippet)

    return start_time, by_agent, snippets_by_agent


def _summarize_agent_activity_with_core_agent(
    handler,
    app_name: str,
    window_label: str,
    info: dict,
    snippets: list[str],
) -> str:
    from armorer.agent import BaseAgent

    fallback = (
        f"Observed {info.get('events', 0)} events for {app_name}, with "
        f"{info.get('suspicious', 0)} flagged events and {info.get('redactions', 0)} redactions. "
        "Review flagged rules and keep sensitive operations gated behind confirmation."
    )
    provider = (handler.config_manager.get_key("MODEL_PROVIDER") or "openai").strip().lower()
    key_by_provider = {
        "openai": "OPENAI_API_KEY",
        "anthropic": "ANTHROPIC_API_KEY",
        "gemini": "GEMINI_API_KEY",
        "openrouter": "OPENROUTER_API_KEY",
    }
    req_key = key_by_provider.get(provider)
    if req_key and not handler.config_manager.get_key(req_key):
        return fallback
    try:
        context_snippets = "\n".join(f"- {s}" for s in snippets[:10]) or "- No snippets captured."
        reason_items = sorted((info.get("reasons") or {}).items(), key=lambda x: x[1], reverse=True)[:8]
        reason_summary = ", ".join(f"{k}({v})" for k, v in reason_items) or "none"
        analyst = BaseAgent()
        analyst.init_agent(
            system_prompt=(
                "You are Armorer Daily Analyst. "
                "Summarize one managed agent's day from sanitized security telemetry. "
                "Be concise, factual, and actionable."
            ),
            agent_name="armorer:core:daily-analyst",
            tools=[],
        )
        user_input = (
            f"Agent: {app_name}\n"
            f"Window: {window_label}\n"
            f"Events: {info.get('events', 0)}\n"
            f"Runtime events: {info.get('runtime_events', 0)}\n"
            f"Flags: {info.get('suspicious', 0)}\n"
            f"Redactions: {info.get('redactions', 0)}\n"
            f"Top rules: {reason_summary}\n\n"
            "Sanitized conversation/runtime snippets:\n"
            f"{context_snippets}\n\n"
            "Return plain text with exactly 4 short bullets:\n"
            "1) What the agent worked on\n"
            "2) Risky behavior observed\n"
            "3) Security posture assessment\n"
            "4) Next action for operator"
        )
        out = analyst.run(user_input=user_input)
        text = str(out or "").strip()
        if not text:
            return fallback
        return text[:1600]
    except Exception:
        return fallback


def _build_daily_report(handler, end_time: datetime, window_hours: int) -> str:
    start_time, by_agent, _snippets = _collect_daily_report_stats(handler, end_time=end_time, window_hours=window_hours)
    total_events = sum(v["events"] for v in by_agent.values())
    total_flags = sum(v["suspicious"] for v in by_agent.values())
    total_redactions = sum(v["redactions"] for v in by_agent.values())

    window_label = f"{start_time.strftime('%Y-%m-%d %H:%M')} -> {end_time.strftime('%Y-%m-%d %H:%M')}"
    lines = [
        "*ARMORER DAILY REPORT*",
        f"*Window:* `{escape_markdown_v2(window_label)}`",
        f"*Totals:* `events:{total_events} flags:{total_flags} redactions:{total_redactions}`",
        "",
    ]

    if not by_agent:
        lines.append("No managed agents found\\.")
        return "\n".join(lines)

    for app_name in sorted(by_agent.keys()):
        info = by_agent[app_name]
        top_reasons = sorted(info["reasons"].items(), key=lambda x: x[1], reverse=True)[:3]
        reason_text = ", ".join(f"{k}({v})" for k, v in top_reasons) if top_reasons else "-"
        lines.extend(
            [
                f"*Agent:* `{escape_markdown_v2(app_name)}`",
                f"\\- `events:{info['events']} runtime:{info['runtime_events']}`",
                f"\\- `flags:{info['suspicious']} redactions:{info['redactions']}`",
                f"\\- top rules: `{escape_markdown_v2(reason_text)}`",
                "",
            ]
        )

    msg = "\n".join(lines).strip()
    if len(msg) > 3900:
        msg = msg[:3900] + "\n\\.\\.\\. truncated"
    return msg


def _generate_agent_pdf_reports(handler, end_time: datetime, window_hours: int) -> list[Path]:
    try:
        from reportlab.lib.pagesizes import LETTER
        from reportlab.lib import colors
        from reportlab.pdfgen import canvas
    except Exception:
        return []

    start_time, by_agent, snippets_by_agent = _collect_daily_report_stats(handler, end_time=end_time, window_hours=window_hours)
    reports_dir = handler.config_dir / "reports"
    reports_dir.mkdir(parents=True, exist_ok=True)
    stamp = end_time.strftime("%Y%m%d")
    generated: list[Path] = []
    window_label = f"{start_time.strftime('%Y-%m-%d %H:%M')} -> {end_time.strftime('%Y-%m-%d %H:%M')}"

    for app_name in sorted(by_agent.keys()):
        info = by_agent[app_name]
        top_reasons = sorted(info["reasons"].items(), key=lambda x: x[1], reverse=True)[:8]
        out_path = reports_dir / f"{app_name}-daily-{stamp}.pdf"
        try:
            c = canvas.Canvas(str(out_path), pagesize=LETTER)
            w, h = LETTER
            y = h - 36

            def draw_header() -> None:
                c.setFillColor(colors.HexColor("#0f172a"))
                c.rect(0, h - 96, w, 96, stroke=0, fill=1)
                c.setFillColor(colors.HexColor("#e2e8f0"))
                c.setFont("Helvetica-Bold", 17)
                c.drawString(36, h - 50, "Armorer Daily Agent Report")
                c.setFont("Helvetica", 10)
                c.drawString(36, h - 70, f"Agent: {app_name}")
                c.drawRightString(w - 36, h - 70, f"Date: {end_time.strftime('%Y-%m-%d')}")
                c.setFillColor(colors.HexColor("#93c5fd"))
                c.setFont("Helvetica", 9)
                c.drawString(36, h - 84, f"Window: {window_label}")
                c.setFillColor(colors.black)

            def draw_footer() -> None:
                c.setFillColor(colors.HexColor("#64748b"))
                c.setFont("Helvetica-Oblique", 8)
                c.drawString(36, 22, "SecureNeural Armorer Guard")
                c.drawRightString(w - 36, 22, f"Generated {datetime.now().strftime('%Y-%m-%d %H:%M')}")

            draw_header()
            y = h - 122

            def line(txt: str, size: int = 11) -> None:
                nonlocal y
                if y < 60:
                    draw_footer()
                    c.showPage()
                    draw_header()
                    y = h - 122
                c.setFillColor(colors.black)
                c.setFont("Helvetica", size)
                c.drawString(50, y, txt[:140])
                y -= 16

            def ensure_space(min_height: int) -> None:
                nonlocal y
                if y < min_height:
                    draw_footer()
                    c.showPage()
                    draw_header()
                    y = h - 122

            def section(title: str) -> None:
                nonlocal y
                ensure_space(90)
                c.setFillColor(colors.HexColor("#1d4ed8"))
                bar_x = 36
                bar_y = y - 14
                bar_w = w - 72
                bar_h = 18
                c.rect(bar_x, bar_y, bar_w, bar_h, stroke=0, fill=1)
                c.setFillColor(colors.white)
                c.setFont("Helvetica-Bold", 11)
                tw = c.stringWidth(title, "Helvetica-Bold", 11)
                tx = bar_x + max(0, (bar_w - tw) / 2)
                ty = bar_y + 5
                c.drawString(tx, ty, title)
                y -= 26
                c.setFillColor(colors.black)

            def wrapped_block(text: str, font_size: int = 10, indent: int = 42) -> None:
                nonlocal y
                width_chars = 105
                for raw_line in str(text or "").splitlines():
                    chunks = wrap(raw_line, width=width_chars) or [""]
                    for chunk in chunks:
                        ensure_space(60)
                        c.setFont("Helvetica", font_size)
                        c.drawString(indent, y, chunk)
                        y -= 14

            def draw_centered_text(x: float, y_pos: float, width: float, text: str, font: str, size: int, color) -> None:
                c.setFont(font, size)
                c.setFillColor(color)
                tw = c.stringWidth(text, font, size)
                tx = x + max(0, (width - tw) / 2)
                c.drawString(tx, y_pos, text)

            c.setTitle(f"Armorer Daily Report - {app_name}")
            section("Scope")

            ensure_space(150)
            card_y = y
            card_w = (w - 96) / 4
            metrics = [
                ("Events", str(info["events"]), "#0ea5e9"),
                ("Runtime", str(info["runtime_events"]), "#22c55e"),
                ("Flags", str(info["suspicious"]), "#f97316"),
                ("Redactions", str(info["redactions"]), "#a855f7"),
            ]
            for i, (label, value, color_hex) in enumerate(metrics):
                x = 36 + i * card_w
                c.setFillColor(colors.HexColor(color_hex))
                box_w = card_w - 8
                c.roundRect(x, card_y - 40, box_w, 36, 6, stroke=0, fill=1)
                draw_centered_text(
                    x=x,
                    y_pos=card_y - 22,
                    width=box_w,
                    text=value,
                    font="Helvetica-Bold",
                    size=13,
                    color=colors.white,
                )
                draw_centered_text(
                    x=x,
                    y_pos=card_y - 34,
                    width=box_w,
                    text=label,
                    font="Helvetica",
                    size=8,
                    color=colors.white,
                )
            y -= 52

            flags = int(info["suspicious"])
            risk_label = "LOW"
            risk_color = "#16a34a"
            if flags >= 10:
                risk_label = "HIGH"
                risk_color = "#dc2626"
            elif flags >= 3:
                risk_label = "MEDIUM"
                risk_color = "#d97706"
            badge_x = 36
            badge_w = 108
            badge_h = 18
            badge_y = y - 20
            c.setFillColor(colors.HexColor(risk_color))
            c.roundRect(badge_x, badge_y, badge_w, badge_h, 6, stroke=0, fill=1)
            draw_centered_text(
                x=badge_x,
                y_pos=badge_y + 5,
                width=badge_w,
                text=f"Risk: {risk_label}",
                font="Helvetica-Bold",
                size=9,
                color=colors.white,
            )
            c.setFillColor(colors.black)
            c.setFont("Helvetica", 10)
            c.drawString(156, y - 8, "Risk is based on suspicious event count in the selected window.")
            y -= 30

            section("Top Matched Rules")
            if top_reasons:
                for reason, count in top_reasons:
                    line(f"- {reason}: {count}")
            else:
                line("- None")

            section("AI Analyst Summary")
            ai_summary = _summarize_agent_activity_with_core_agent(
                handler,
                app_name=app_name,
                window_label=window_label,
                info=info,
                snippets=snippets_by_agent.get(app_name, []),
            )
            wrapped_block(ai_summary)

            section("Sample Sanitized Snippets")
            samples = snippets_by_agent.get(app_name, [])[:10]
            if samples:
                for s in samples:
                    wrapped_block(f"- {s}", font_size=9, indent=44)
            else:
                line("- No snippets captured.")

            draw_footer()
            c.save()
            generated.append(out_path)
        except Exception:
            continue

    return generated


def _maybe_send_daily_report(handler) -> None:
    from armorer.core.guard import _send_guard_notification, _send_guard_document

    now_ts = time.time()
    if now_ts - handler._daily_report_last_check_ts < 60:
        return
    handler._daily_report_last_check_ts = now_ts

    enabled, report_time, window_hours = _daily_report_config(handler)
    if not enabled:
        return

    chat_id = handler.config_manager.get_key("TELEGRAM_ALERT_CHAT_ID")
    if not chat_id:
        return

    now = datetime.now()
    hh, mm = report_time.split(":")
    target = now.replace(hour=int(hh), minute=int(mm), second=0, microsecond=0)
    if now < target:
        return

    state = _load_daily_report_state(handler)
    last_sent = str(state.get("last_sent_date") or "")
    today = now.strftime("%Y-%m-%d")
    if last_sent == today:
        return

    try:
        msg = _build_daily_report(handler, end_time=now, window_hours=window_hours)
        _send_guard_notification(handler, str(chat_id), msg)
        pdf_reports = _generate_agent_pdf_reports(handler, end_time=now, window_hours=window_hours)
        if pdf_reports:
            for pdf in pdf_reports:
                caption = f"Armorer daily agent report: {pdf.name}"
                _send_guard_document(handler, str(chat_id), pdf, caption=caption)
        state["last_sent_date"] = today
        state["last_sent_ts"] = now_ts
        _save_daily_report_state(handler, state)
        logger.info("📊 Daily guard report sent to Telegram.")
    except Exception as e:
        logger.warning("Daily report send failed: %s", e)
