"""Self-service EU AI Act gap assessment API.

Provides a web-facing endpoint that wraps MultiFrameworkAssessor and
RiskClassifier to produce an indicative EU AI Act gap assessment with
a downloadable PDF report.

Constitutional Hash: 608508a9bd224290
"""

from __future__ import annotations

import asyncio
import base64
import io
import time
from collections import defaultdict
from typing import Any, Literal

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field

from acgs_lite._meta import VERSION
from acgs_lite.compliance.multi_framework import MultiFrameworkAssessor
from acgs_lite.eu_ai_act.risk_classification import RiskClassifier, SystemDescription

# ---------------------------------------------------------------------------
# Request / Response models
# ---------------------------------------------------------------------------

class SystemAssessmentRequest(BaseModel):
    """Input for the EU AI Act gap assessment."""

    system_type: Literal[
        "chatbot",
        "recommendation",
        "content_generation",
        "hiring_hr",
        "medical",
        "financial",
        "autonomous",
        "other",
    ]
    deployment_region: Literal["eu", "non_eu"]
    system_description: str = Field(max_length=10_000)
    data_categories: list[
        Literal["personal_data", "biometric", "health", "financial", "minor_data"]
    ] = Field(default_factory=list)
    human_oversight_level: Literal["full", "partial", "minimal", "none"] = "partial"

    # Annex III flags
    employment: bool = False
    law_enforcement: bool = False
    biometric_processing: bool = False
    social_scoring: bool = False
    education: bool = False


class AssessmentResponse(BaseModel):
    """Output of the EU AI Act gap assessment."""

    risk_level: str
    top_gaps: list[str]
    compliance_score: float
    framework_scores: dict[str, float]
    pdf_base64: str
    disclaimer: str = (
        "This is an automated indicative assessment. "
        "It does not constitute legal advice. "
        "Consult qualified legal counsel for binding compliance opinions."
    )


# ---------------------------------------------------------------------------
# Rate limiter (in-memory, per-IP)
# ---------------------------------------------------------------------------

_RATE_LIMIT = 5
_RATE_WINDOW = 3600  # seconds

_request_log: dict[str, list[float]] = defaultdict(list)


def _check_rate_limit(ip: str) -> bool:
    """Return True if the request is allowed, False if rate-limited."""
    now = time.monotonic()
    window_start = now - _RATE_WINDOW
    _request_log[ip] = [t for t in _request_log[ip] if t > window_start]
    if len(_request_log[ip]) >= _RATE_LIMIT:
        return False
    _request_log[ip].append(now)
    return True


# ---------------------------------------------------------------------------
# Domain mapping
# ---------------------------------------------------------------------------

_SYSTEM_TYPE_TO_DOMAIN: dict[str, str] = {
    "chatbot": "chatbot",
    "recommendation": "recommendation",
    "content_generation": "content_generation",
    "hiring_hr": "employment",
    "medical": "healthcare",
    "financial": "credit_scoring",
    "autonomous": "critical_infrastructure",
    "other": "general",
}

_OVERSIGHT_TO_INT: dict[str, int] = {
    "full": 1,
    "partial": 2,
    "minimal": 4,
    "none": 5,
}


# ---------------------------------------------------------------------------
# Core assessment logic (sync, runs in executor)
# ---------------------------------------------------------------------------

def _run_assessment(req: SystemAssessmentRequest) -> dict[str, Any]:
    """Run the full assessment pipeline. Synchronous, CPU-bound."""
    domain = _SYSTEM_TYPE_TO_DOMAIN.get(req.system_type, "general")

    # Build system description for MultiFrameworkAssessor
    sys_desc: dict[str, Any] = {
        "system_id": f"web-assess-{int(time.time())}",
        "jurisdiction": "european_union",
        "domain": domain,
        "purpose": req.system_description[:200],
        "deployment_region": req.deployment_region,
    }

    # Run multi-framework assessment (EU frameworks only via jurisdiction)
    assessor = MultiFrameworkAssessor()
    report = assessor.assess(sys_desc)

    # Run risk classification with Annex III flags
    classifier = RiskClassifier()
    sys_info = SystemDescription(
        system_id=sys_desc["system_id"],
        purpose=req.system_description[:200],
        domain=domain,
        autonomy_level=_OVERSIGHT_TO_INT.get(req.human_oversight_level, 2),
        human_oversight=req.human_oversight_level in ("full", "partial"),
        biometric_processing=req.biometric_processing or "biometric" in req.data_categories,
        law_enforcement=req.law_enforcement,
        education=req.education,
        employment=req.employment,
        social_scoring=req.social_scoring,
    )
    classification = classifier.classify(sys_info)

    # Generate PDF to BytesIO
    report_data = report.to_dict()
    pdf_bytes = _generate_pdf_bytes(report_data)

    # Extract top gaps from cross-framework gaps + recommendations
    top_gaps = list(report.cross_framework_gaps[:3])
    if len(top_gaps) < 3:
        top_gaps.extend(list(report.recommendations[: 3 - len(top_gaps)]))

    # Framework scores
    fw_scores: dict[str, float] = {}
    for fw_id, fw_result in report.by_framework.items():
        fw_scores[fw_id] = fw_result.score if hasattr(fw_result, "score") else 0.0

    return {
        "risk_level": classification.level.value if hasattr(classification.level, "value") else str(classification.level),
        "top_gaps": top_gaps,
        "compliance_score": report.overall_score,
        "framework_scores": fw_scores,
        "pdf_base64": base64.b64encode(pdf_bytes).decode("ascii"),
    }


def _generate_pdf_bytes(report_data: dict[str, Any]) -> bytes:
    """Generate a PDF report and return raw bytes (no filesystem writes)."""
    from fpdf import FPDF


    now_str = time.strftime("%Y-%m-%d %H:%M UTC", time.gmtime())
    overall = report_data.get("overall_score", 0.0)
    frameworks = report_data.get("frameworks_assessed", [])
    by_framework = report_data.get("by_framework", {})

    def _safe(text: str) -> str:
        return (
            text.replace("\u2014", "--")
            .replace("\u2013", "-")
            .replace("\u2018", "'")
            .replace("\u2019", "'")
            .replace("\u201c", '"')
            .replace("\u201d", '"')
            .replace("\u2026", "...")
            .replace("\u2022", "*")
            .replace("\u2713", "Y")
            .replace("\u2717", "X")
            .replace("\u2705", "[OK]")
            .replace("\u274c", "[FAIL]")
            .replace("\u2b1c", "[  ]")
            .replace("\u2796", "[-]")
        )

    pdf = FPDF()
    pdf.set_auto_page_break(auto=True, margin=15)

    # Cover page
    pdf.add_page()
    pdf.set_font("Helvetica", "B", 24)
    pdf.cell(0, 20, _safe("EU AI Act Gap Assessment"), ln=True, align="C")
    pdf.set_font("Helvetica", "", 12)
    pdf.cell(0, 10, _safe(f"Generated: {now_str}"), ln=True, align="C")
    pdf.ln(10)
    pdf.set_font("Helvetica", "B", 16)
    verdict = "STRONG" if overall >= 0.7 else "MODERATE" if overall >= 0.4 else "AT RISK"
    pdf.cell(0, 10, _safe(f"Overall Score: {overall:.0%} - {verdict}"), ln=True, align="C")
    pdf.ln(10)
    pdf.set_font("Helvetica", "", 10)
    pdf.multi_cell(
        0, 6,
        _safe(
            "DISCLAIMER: This is an automated indicative assessment. "
            "It does not constitute legal advice. Consult qualified legal "
            "counsel for binding EU AI Act compliance opinions."
        ),
    )

    # Framework summaries
    for fw_id in frameworks:
        fw = by_framework.get(fw_id, {})
        score = fw.get("score", 0.0) if isinstance(fw, dict) else 0.0
        gaps = fw.get("gaps", []) if isinstance(fw, dict) else []

        pdf.add_page()
        pdf.set_font("Helvetica", "B", 14)
        pdf.cell(0, 10, _safe(f"Framework: {fw_id} (Score: {score:.0%})"), ln=True)
        pdf.ln(5)

        if gaps:
            pdf.set_font("Helvetica", "B", 11)
            pdf.cell(0, 8, "Compliance Gaps:", ln=True)
            pdf.set_font("Helvetica", "", 9)
            for gap in gaps[:10]:
                gap_text = gap if isinstance(gap, str) else str(gap)
                # Truncate long gap text to avoid layout overflow
                gap_text = gap_text[:120] + "..." if len(gap_text) > 120 else gap_text
                pdf.cell(0, 5, _safe(f"- {gap_text}"), ln=True)

    # Recommendations
    recs = report_data.get("recommendations", [])
    if recs:
        pdf.add_page()
        pdf.set_font("Helvetica", "B", 14)
        pdf.cell(0, 10, "Prioritized Recommendations", ln=True)
        pdf.ln(5)
        pdf.set_font("Helvetica", "", 10)
        for i, rec in enumerate(recs[:10], 1):
            pdf.multi_cell(0, 5, _safe(f"{i}. {rec}"))
            pdf.ln(2)

    buf = io.BytesIO()
    pdf.output(buf)
    return buf.getvalue()


# ---------------------------------------------------------------------------
# FastAPI app factory
# ---------------------------------------------------------------------------

def create_assess_app() -> FastAPI:
    """Create the assessment FastAPI application."""
    app = FastAPI(
        title="ACGS EU AI Act Assessment",
        version=VERSION,
        docs_url="/api/docs",
    )

    app.add_middleware(
        CORSMiddleware,
        allow_origins=[
            "https://acgs.ai",
            "https://www.acgs.ai",
            "http://localhost:5173",
            "http://localhost:4173",
        ],
        allow_methods=["POST", "GET", "OPTIONS"],
        allow_headers=["Content-Type"],
    )

    @app.get("/api/health")
    async def health() -> dict[str, str]:
        return {"status": "ok", "version": VERSION}

    @app.post("/api/assess")
    async def assess(req: SystemAssessmentRequest, request: Request) -> JSONResponse:
        client_ip = request.client.host if request.client else "unknown"
        if not _check_rate_limit(client_ip):
            return JSONResponse(
                status_code=429,
                content={"detail": "Rate limit exceeded. Maximum 5 assessments per hour."},
            )

        loop = asyncio.get_running_loop()
        result = await loop.run_in_executor(None, _run_assessment, req)
        response = AssessmentResponse(**result)
        return JSONResponse(content=response.model_dump())

    return app


# Convenience: allow `uvicorn acgs_lite.assess_api:app`
app = create_assess_app()
