"""Report generator for AYN Antivirus.

Produces scan reports in plain-text, JSON, and HTML formats from
:class:`ScanResult` / :class:`FullScanResult` dataclasses.
"""

from __future__ import annotations

import html as html_mod
import json
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

from ayn_antivirus import __version__
from ayn_antivirus.core.engine import (
    FullScanResult,
    ScanResult,
    ThreatInfo,
)
from ayn_antivirus.utils.helpers import format_duration, format_size, get_system_info

# Type alias for either result kind.
AnyResult = Union[ScanResult, FullScanResult]


class ReportGenerator:
    """Create scan reports in multiple output formats."""

    # ------------------------------------------------------------------
    # Plain text
    # ------------------------------------------------------------------

    @staticmethod
    def generate_text(result: AnyResult) -> str:
        """Render a human-readable plain-text report."""
        threats, meta = _extract(result)
        lines: List[str] = []

        lines.append("=" * 72)
        lines.append("  AYN ANTIVIRUS — SCAN REPORT")
        lines.append("=" * 72)
        lines.append("")
        lines.append(f"  Generated : {datetime.utcnow().isoformat()}")
        lines.append(f"  Version   : {__version__}")
        lines.append(f"  Scan ID   : {meta.get('scan_id', 'N/A')}")
        lines.append(f"  Scan Type : {meta.get('scan_type', 'N/A')}")
        lines.append(f"  Duration  : {format_duration(meta.get('duration', 0))}")
        lines.append("")

        # Summary.
        sev_counts = _severity_counts(threats)
        lines.append("-" * 72)
        lines.append("  SUMMARY")
        lines.append("-" * 72)
        lines.append(f"  Files scanned  : {meta.get('files_scanned', 0)}")
        lines.append(f"  Files skipped  : {meta.get('files_skipped', 0)}")
        lines.append(f"  Threats found  : {len(threats)}")
        lines.append(f"    CRITICAL     : {sev_counts.get('CRITICAL', 0)}")
        lines.append(f"    HIGH         : {sev_counts.get('HIGH', 0)}")
        lines.append(f"    MEDIUM       : {sev_counts.get('MEDIUM', 0)}")
        lines.append(f"    LOW          : {sev_counts.get('LOW', 0)}")
        lines.append("")

        # Threat table.
        if threats:
            lines.append("-" * 72)
            lines.append("  THREATS")
            lines.append("-" * 72)
            hdr = f"  {'#':>3}  {'Severity':<10} {'Threat Name':<30} {'File'}"
            lines.append(hdr)
            lines.append("  " + "-" * 68)
            for idx, t in enumerate(threats, 1):
                sev = _sev_str(t)
                name = t.threat_name[:30]
                fpath = t.path[:60]
                lines.append(f"  {idx:>3}  {sev:<10} {name:<30} {fpath}")
            lines.append("")

        # System info.
        try:
            info = get_system_info()
            lines.append("-" * 72)
            lines.append("  SYSTEM INFORMATION")
            lines.append("-" * 72)
            lines.append(f"  Hostname : {info['hostname']}")
            lines.append(f"  OS       : {info['os_pretty']}")
            lines.append(f"  CPUs     : {info['cpu_count']}")
            lines.append(f"  Memory   : {info['memory_total_human']}")
            lines.append(f"  Uptime   : {info['uptime_human']}")
            lines.append("")
        except Exception:
            pass

        lines.append("=" * 72)
        lines.append(f"  Report generated by AYN Antivirus v{__version__}")
        lines.append("=" * 72)
        return "\n".join(lines) + "\n"

    # ------------------------------------------------------------------
    # JSON
    # ------------------------------------------------------------------

    @staticmethod
    def generate_json(result: AnyResult) -> str:
        """Render a machine-readable JSON report."""
        threats, meta = _extract(result)
        sev_counts = _severity_counts(threats)

        try:
            sys_info = get_system_info()
        except Exception:
            sys_info = {}

        report: Dict[str, Any] = {
            "generator": f"ayn-antivirus v{__version__}",
            "generated_at": datetime.utcnow().isoformat(),
            "scan": {
                "scan_id": meta.get("scan_id"),
                "scan_type": meta.get("scan_type"),
                "start_time": meta.get("start_time"),
                "end_time": meta.get("end_time"),
                "duration_seconds": meta.get("duration"),
                "files_scanned": meta.get("files_scanned", 0),
                "files_skipped": meta.get("files_skipped", 0),
            },
            "summary": {
                "total_threats": len(threats),
                "by_severity": sev_counts,
            },
            "threats": [
                {
                    "path": t.path,
                    "threat_name": t.threat_name,
                    "threat_type": t.threat_type.name if hasattr(t.threat_type, "name") else str(t.threat_type),
                    "severity": _sev_str(t),
                    "detector": t.detector_name,
                    "details": t.details,
                    "file_hash": t.file_hash,
                    "timestamp": t.timestamp.isoformat() if hasattr(t.timestamp, "isoformat") else str(t.timestamp),
                }
                for t in threats
            ],
            "system": sys_info,
        }
        return json.dumps(report, indent=2, default=str)

    # ------------------------------------------------------------------
    # HTML
    # ------------------------------------------------------------------

    @staticmethod
    def generate_html(result: AnyResult) -> str:
        """Render a professional HTML report with dark-theme CSS."""
        threats, meta = _extract(result)
        sev_counts = _severity_counts(threats)
        now = datetime.utcnow()
        esc = html_mod.escape

        try:
            sys_info = get_system_info()
        except Exception:
            sys_info = {}

        total_threats = len(threats)
        status_class = "clean" if total_threats == 0 else "infected"

        # --- Build threat table rows ---
        threat_rows = []
        for idx, t in enumerate(threats, 1):
            sev = _sev_str(t)
            sev_lower = sev.lower()
            ttype = t.threat_type.name if hasattr(t.threat_type, "name") else str(t.threat_type)
            threat_rows.append(
                f"<tr>"
                f'<td class="idx">{idx}</td>'
                f"<td>{esc(t.path)}</td>"
                f"<td>{esc(t.threat_name)}</td>"
                f"<td>{esc(ttype)}</td>"
                f'<td><span class="badge badge-{sev_lower}">{sev}</span></td>'
                f"<td>{esc(t.detector_name)}</td>"
                f'<td class="hash">{esc(t.file_hash[:16])}{"…" if len(t.file_hash) > 16 else ""}</td>'
                f"</tr>"
            )

        threat_table = "\n".join(threat_rows) if threat_rows else (
            '<tr><td colspan="7" class="empty">No threats detected ✅</td></tr>'
        )

        # --- System info rows ---
        sys_rows = ""
        if sys_info:
            sys_rows = (
                f"<tr><td>Hostname</td><td>{esc(str(sys_info.get('hostname', '')))}</td></tr>"
                f"<tr><td>Operating System</td><td>{esc(str(sys_info.get('os_pretty', '')))}</td></tr>"
                f"<tr><td>Architecture</td><td>{esc(str(sys_info.get('architecture', '')))}</td></tr>"
                f"<tr><td>CPUs</td><td>{sys_info.get('cpu_count', '?')}</td></tr>"
                f"<tr><td>Memory</td><td>{esc(str(sys_info.get('memory_total_human', '')))}"
                f" ({sys_info.get('memory_percent', '?')}% used)</td></tr>"
                f"<tr><td>Uptime</td><td>{esc(str(sys_info.get('uptime_human', '')))}</td></tr>"
            )

        html = f"""\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AYN Antivirus — Scan Report</title>
<style>
{_CSS}
</style>
</head>
<body>

<!-- Header -->
<header>
  <div class="logo">⚔️ AYN ANTIVIRUS</div>
  <div class="subtitle">Scan Report — {esc(now.strftime("%Y-%m-%d %H:%M:%S"))}</div>
</header>

<!-- Summary cards -->
<section class="cards">
  <div class="card">
    <div class="card-value">{meta.get("files_scanned", 0)}</div>
    <div class="card-label">Files Scanned</div>
  </div>
  <div class="card card-{status_class}">
    <div class="card-value">{total_threats}</div>
    <div class="card-label">Threats Found</div>
  </div>
  <div class="card card-critical">
    <div class="card-value">{sev_counts.get("CRITICAL", 0)}</div>
    <div class="card-label">Critical</div>
  </div>
  <div class="card card-high">
    <div class="card-value">{sev_counts.get("HIGH", 0)}</div>
    <div class="card-label">High</div>
  </div>
  <div class="card card-medium">
    <div class="card-value">{sev_counts.get("MEDIUM", 0)}</div>
    <div class="card-label">Medium</div>
  </div>
  <div class="card card-low">
    <div class="card-value">{sev_counts.get("LOW", 0)}</div>
    <div class="card-label">Low</div>
  </div>
</section>

<!-- Scan details -->
<section class="details">
  <h2>Scan Details</h2>
  <table class="info-table">
    <tr><td>Scan ID</td><td>{esc(str(meta.get("scan_id", "N/A")))}</td></tr>
    <tr><td>Scan Type</td><td>{esc(str(meta.get("scan_type", "N/A")))}</td></tr>
    <tr><td>Duration</td><td>{esc(format_duration(meta.get("duration", 0)))}</td></tr>
    <tr><td>Files Scanned</td><td>{meta.get("files_scanned", 0)}</td></tr>
    <tr><td>Files Skipped</td><td>{meta.get("files_skipped", 0)}</td></tr>
  </table>
</section>

<!-- Threat table -->
<section class="threats">
  <h2>Threat Details</h2>
  <table class="threat-table">
    <thead>
      <tr>
        <th>#</th>
        <th>File Path</th>
        <th>Threat Name</th>
        <th>Type</th>
        <th>Severity</th>
        <th>Detector</th>
        <th>Hash</th>
      </tr>
    </thead>
    <tbody>
      {threat_table}
    </tbody>
  </table>
</section>

<!-- System info -->
<section class="system">
  <h2>System Information</h2>
  <table class="info-table">
    {sys_rows}
  </table>
</section>

<!-- Footer -->
<footer>
  Generated by AYN Antivirus v{__version__} &mdash; {esc(now.isoformat())}
</footer>

</body>
</html>
"""
        return html

    # ------------------------------------------------------------------
    # File output
    # ------------------------------------------------------------------

    @staticmethod
    def save_report(content: str, filepath: str | Path) -> None:
        """Write *content* to *filepath*, creating parent dirs if needed."""
        fp = Path(filepath)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content, encoding="utf-8")


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _extract(result: AnyResult) -> tuple:
    """Return ``(threats_list, meta_dict)`` from either result type."""
    if isinstance(result, FullScanResult):
        sr = result.file_scan
        threats = list(sr.threats)
    elif isinstance(result, ScanResult):
        sr = result
        threats = list(sr.threats)
    else:
        sr = result
        threats = []

    meta: Dict[str, Any] = {
        "scan_id": getattr(sr, "scan_id", None),
        "scan_type": sr.scan_type.value if hasattr(sr, "scan_type") else None,
        "start_time": sr.start_time.isoformat() if hasattr(sr, "start_time") and sr.start_time else None,
        "end_time": sr.end_time.isoformat() if hasattr(sr, "end_time") and sr.end_time else None,
        "duration": sr.duration_seconds if hasattr(sr, "duration_seconds") else 0,
        "files_scanned": getattr(sr, "files_scanned", 0),
        "files_skipped": getattr(sr, "files_skipped", 0),
    }
    return threats, meta


def _sev_str(threat: ThreatInfo) -> str:
    """Return the severity as an uppercase string."""
    sev = threat.severity
    if hasattr(sev, "name"):
        return sev.name
    return str(sev).upper()


def _severity_counts(threats: List[ThreatInfo]) -> Dict[str, int]:
    counts: Dict[str, int] = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
    for t in threats:
        key = _sev_str(t)
        counts[key] = counts.get(key, 0) + 1
    return counts


# ---------------------------------------------------------------------------
# Embedded CSS (dark theme)
# ---------------------------------------------------------------------------
_CSS = """\
:root {
  --bg:          #0f1117;
  --surface:     #1a1d27;
  --border:      #2a2d3a;
  --text:        #e0e0e0;
  --text-dim:    #8b8fa3;
  --accent:      #00bcd4;
  --critical:    #ff1744;
  --high:        #ff9100;
  --medium:      #ffea00;
  --low:         #00e676;
  --clean:       #00e676;
  --infected:    #ff1744;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: 'Segoe UI', 'Inter', system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  padding: 0;
}

header {
  background: linear-gradient(135deg, #1a1d27 0%, #0d1117 100%);
  border-bottom: 2px solid var(--accent);
  text-align: center;
  padding: 2rem 1rem;
}

header .logo {
  font-size: 2rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 0.1em;
}

header .subtitle {
  color: var(--text-dim);
  font-size: 0.95rem;
  margin-top: 0.3rem;
}

section {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1.5rem;
}

h2 {
  color: var(--accent);
  font-size: 1.25rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
}

/* Summary cards */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1.5rem;
}

.card {
  flex: 1 1 140px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1.2rem 1rem;
  text-align: center;
}

.card-value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--text);
}

.card-label {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-top: 0.2rem;
}

.card-clean  .card-value { color: var(--clean); }
.card-infected .card-value { color: var(--infected); }
.card-critical .card-value { color: var(--critical); }
.card-high   .card-value { color: var(--high); }
.card-medium .card-value { color: var(--medium); }
.card-low    .card-value { color: var(--low); }

/* Tables */
table {
  width: 100%;
  border-collapse: collapse;
}

.info-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border);
}

.info-table td:first-child {
  color: var(--text-dim);
  width: 180px;
  font-weight: 600;
}

.threat-table {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.threat-table thead th {
  background: #12141c;
  color: var(--accent);
  padding: 0.7rem 0.75rem;
  text-align: left;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.threat-table tbody td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
  word-break: break-all;
}

.threat-table tbody tr:hover {
  background: rgba(0, 188, 212, 0.06);
}

.threat-table .idx { color: var(--text-dim); width: 40px; }
.threat-table .hash { font-family: monospace; color: var(--text-dim); font-size: 0.8rem; }
.threat-table .empty { text-align: center; color: var(--clean); padding: 2rem; font-size: 1.1rem; }

/* Severity badges */
.badge {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.badge-critical { background: rgba(255,23,68,0.15); color: var(--critical); border: 1px solid var(--critical); }
.badge-high     { background: rgba(255,145,0,0.15); color: var(--high);     border: 1px solid var(--high); }
.badge-medium   { background: rgba(255,234,0,0.12); color: var(--medium);   border: 1px solid var(--medium); }
.badge-low      { background: rgba(0,230,118,0.12); color: var(--low);      border: 1px solid var(--low); }

/* Footer */
footer {
  text-align: center;
  color: var(--text-dim);
  font-size: 0.8rem;
  padding: 2rem 1rem;
  border-top: 1px solid var(--border);
  margin-top: 3rem;
}

/* System info */
.system { margin-bottom: 2rem; }
"""
