"""
Compliance HTTP endpoints — Wazuh SCA results + on-demand refresh.

Endpoint map:

  GET  /api/v1/compliance/summary                  — global dashboard view
  GET  /api/v1/compliance/assets                   — per-asset roll-up list
  GET  /api/v1/compliance/{asset_id}               — policies for one asset
  GET  /api/v1/compliance/{asset_id}/{policy_id}/checks  — deep-dive
  POST /api/v1/compliance/refresh                  — refresh ALL assets
  POST /api/v1/compliance/{asset_id}/refresh       — refresh ONE asset

All reads accept any authenticated user; refresh requires editor role.
"""
from __future__ import annotations

from typing import List, Optional
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File
from pydantic import BaseModel
from sqlalchemy import func
from sqlalchemy.orm import Session

from app.auth.dependencies import RequireEditor, get_current_user, RequireAdmin
from app.database import get_db
from app.models.asset import Asset
from app.models.compliance import (
    ComplianceCheck, ComplianceResult, ComplianceImpact,
)
from app.models.user import User
from app.services.compliance_service import (
    fetch_policy_checks,
    refresh_all_compliance,
    refresh_asset_compliance,
)
from app.services.compliance_impact_import import import_impact_csv_files
from app.services.urs_service import (
    compute_urs, compute_urs_for_all, get_urs_trend, urs_severity,
)

router = APIRouter(prefix="/api/v1/compliance", tags=["Compliance"])


# ---------- response schemas ----------

class ComplianceResultResponse(BaseModel):
    id: int
    asset_id: int
    policy_id: str
    policy_name: Optional[str]
    policy_description: Optional[str]
    total_checks: int
    pass_count: int
    fail_count: int
    not_applicable_count: int
    score: Optional[float]
    end_scan: Optional[datetime]
    last_synced: datetime

    class Config:
        from_attributes = True


class ComplianceCheckResponse(BaseModel):
    id: int
    check_id: str
    title: Optional[str]
    description: Optional[str]
    rationale: Optional[str]
    remediation: Optional[str]
    result: str
    severity: Optional[str]

    class Config:
        from_attributes = True


class ComplianceAssetSummary(BaseModel):
    asset_id: int
    hostname: str
    operating_system: Optional[str]
    policy_count: int
    avg_score: Optional[float]
    worst_score: Optional[float]
    worst_policy: Optional[str]
    last_synced: Optional[datetime]


class ComplianceGlobalSummary(BaseModel):
    assets_with_compliance: int
    total_policies: int
    avg_score_all: Optional[float]
    pass_total: int
    fail_total: int
    not_applicable_total: int
    worst_offenders: List[ComplianceAssetSummary]


# ---------- read endpoints ----------

@router.get("/summary", response_model=ComplianceGlobalSummary)
def get_compliance_summary(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Global compliance dashboard widget feed."""
    rows = db.query(ComplianceResult).all()
    assets_with_compliance = len({r.asset_id for r in rows})
    total_policies = len(rows)
    scored = [r.score for r in rows if r.score is not None]
    avg_score_all = round(sum(scored) / len(scored), 2) if scored else None
    pass_total = sum(r.pass_count for r in rows)
    fail_total = sum(r.fail_count for r in rows)
    na_total = sum(r.not_applicable_count for r in rows)

    # Worst-offender list: 5 assets by lowest avg score
    per_asset: dict = {}
    for r in rows:
        per_asset.setdefault(r.asset_id, []).append(r)
    summaries: List[ComplianceAssetSummary] = []
    for aid, results in per_asset.items():
        asset = db.query(Asset).filter(Asset.id == aid).first()
        if not asset:
            continue
        scored = [(r.score, r) for r in results if r.score is not None]
        avg = round(sum(s for s, _ in scored) / len(scored), 2) if scored else None
        worst_score = min((s for s, _ in scored), default=None)
        worst_pol = None
        if worst_score is not None:
            worst_pol = next(
                (r.policy_name or r.policy_id for s, r in scored if s == worst_score),
                None,
            )
        summaries.append(ComplianceAssetSummary(
            asset_id=aid,
            hostname=asset.hostname,
            operating_system=asset.operating_system,
            policy_count=len(results),
            avg_score=avg,
            worst_score=worst_score,
            worst_policy=worst_pol,
            last_synced=max((r.last_synced for r in results), default=None),
        ))
    # Lowest avg score first; assets with no score sink to the bottom.
    summaries.sort(key=lambda s: (s.avg_score is None, s.avg_score or 0))

    return ComplianceGlobalSummary(
        assets_with_compliance=assets_with_compliance,
        total_policies=total_policies,
        avg_score_all=avg_score_all,
        pass_total=pass_total,
        fail_total=fail_total,
        not_applicable_total=na_total,
        worst_offenders=summaries[:5],
    )


@router.get("/assets", response_model=List[ComplianceAssetSummary])
def list_compliance_assets(
    limit: int = Query(100, le=500),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Per-asset compliance roll-up — for a future compliance landing page."""
    # Re-uses the same aggregation as /summary but returns the full list.
    summary = get_compliance_summary(db=db, current_user=current_user)
    # FastAPI handed us a ComplianceGlobalSummary instance — refetch
    # the per-asset map by calling the same logic directly here.
    rows = db.query(ComplianceResult).all()
    per_asset: dict = {}
    for r in rows:
        per_asset.setdefault(r.asset_id, []).append(r)
    summaries: List[ComplianceAssetSummary] = []
    for aid, results in per_asset.items():
        asset = db.query(Asset).filter(Asset.id == aid).first()
        if not asset:
            continue
        scored = [(r.score, r) for r in results if r.score is not None]
        avg = round(sum(s for s, _ in scored) / len(scored), 2) if scored else None
        worst_score = min((s for s, _ in scored), default=None)
        worst_pol = None
        if worst_score is not None:
            worst_pol = next(
                (r.policy_name or r.policy_id for s, r in scored if s == worst_score),
                None,
            )
        summaries.append(ComplianceAssetSummary(
            asset_id=aid,
            hostname=asset.hostname,
            operating_system=asset.operating_system,
            policy_count=len(results),
            avg_score=avg,
            worst_score=worst_score,
            worst_policy=worst_pol,
            last_synced=max((r.last_synced for r in results), default=None),
        ))
    summaries.sort(key=lambda s: (s.avg_score is None, s.avg_score or 0))
    return summaries[:limit]


@router.get("/{asset_id:int}", response_model=List[ComplianceResultResponse])
def list_asset_compliance(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """All SCA policy results for one asset (read from cache)."""
    rows = (
        db.query(ComplianceResult)
        .filter(ComplianceResult.asset_id == asset_id)
        .order_by(ComplianceResult.score.asc().nullslast())
        .all()
    )
    return rows


@router.get(
    "/{asset_id:int}/{policy_id}/checks",
    response_model=List[ComplianceCheckResponse],
)
def get_asset_policy_checks(
    asset_id: int,
    policy_id: str,
    refresh: bool = Query(
        False,
        description="If true, re-fetch check detail from Wazuh before returning.",
    ),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    Per-check detail. Cached in compliance_checks; pass ``?refresh=true``
    to force a fresh fetch from Wazuh (slower but always current).
    """
    result_row = (
        db.query(ComplianceResult)
        .filter(
            ComplianceResult.asset_id == asset_id,
            ComplianceResult.policy_id == policy_id,
        )
        .first()
    )
    if not result_row:
        raise HTTPException(
            404,
            f"No compliance result for asset {asset_id} / policy {policy_id} — "
            "run /compliance/{asset_id}/refresh first."
        )
    if refresh:
        return fetch_policy_checks(db, asset_id, policy_id)
    cached = (
        db.query(ComplianceCheck)
        .filter(ComplianceCheck.result_id == result_row.id)
        .order_by(ComplianceCheck.result.desc(), ComplianceCheck.id.asc())
        .all()
    )
    if not cached:
        # Lazy first-fetch — no cached rows yet.
        return fetch_policy_checks(db, asset_id, policy_id)
    return cached


# ---------- mutation endpoints ----------

@router.post("/{asset_id:int}/refresh")
def refresh_one_asset(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """Trigger an SCA refresh for one asset (synchronous, fast)."""
    try:
        return refresh_asset_compliance(db, asset_id)
    except ValueError as e:
        raise HTTPException(404, str(e))


@router.post("/impacts/import")
async def import_compliance_impacts(
    files: List[UploadFile] = File(..., description="CIS-Benchmark CSVs"),
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db),
):
    """
    Bulk-upload one or more CSV files mapping CIS check ids to impact
    ratings (0-100). Benchmark label is auto-derived from filename
    unless the CSV carries an explicit `benchmark` column.

    Idempotent — repeat uploads upsert by (cis_id, benchmark).
    """
    payloads: List = []
    for f in files:
        blob = await f.read()
        payloads.append((f.filename or "upload.csv", blob))
    return import_impact_csv_files(db, payloads)


@router.get("/impacts/stats")
def get_impact_stats(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Quick visibility into what impact data is loaded."""
    rows = (
        db.query(
            ComplianceImpact.benchmark,
            func.count(ComplianceImpact.id).label("count"),
            func.avg(ComplianceImpact.impact).label("avg_impact"),
            func.min(ComplianceImpact.impact).label("min_impact"),
            func.max(ComplianceImpact.impact).label("max_impact"),
        )
        .group_by(ComplianceImpact.benchmark)
        .order_by(ComplianceImpact.benchmark)
        .all()
    )
    return [
        {
            "benchmark": r.benchmark,
            "count": r.count,
            "avg_impact": round(float(r.avg_impact), 1) if r.avg_impact else None,
            "min_impact": r.min_impact,
            "max_impact": r.max_impact,
        }
        for r in rows
    ]


# ---------- URS endpoints ----------

class URSResponse(BaseModel):
    asset_id: int
    hostname: Optional[str] = None
    avs: Optional[float] = None
    ass: Optional[float] = None
    urs: Optional[float] = None
    severity: str = "NONE"
    criticality: Optional[str] = None
    criticality_factor: Optional[float] = None
    policy_count: Optional[int] = None
    trend_7d: Optional[float] = None


@router.get("/urs/{asset_id:int}", response_model=URSResponse)
def get_asset_urs(
    asset_id: int,
    avs_mode: str = Query("hybrid", pattern="^(hybrid|avg|max)$"),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    Compute and persist today's URS snapshot for one asset.

    avs_mode picks how AVS is derived from CPR scores:
      hybrid (default) — 0.7 × avg + 0.3 × max
      avg              — mean of CPR
      max              — single highest CPR
    """
    result = compute_urs(db, asset_id, avs_mode=avs_mode, persist_snapshot=True)
    asset = db.query(Asset).filter(Asset.id == asset_id).first()
    trend = get_urs_trend(db, asset_id, days=7)
    return URSResponse(
        asset_id=asset_id,
        hostname=asset.hostname if asset else None,
        avs=result["avs"],
        ass=result["ass"],
        urs=result["urs"],
        severity=result["severity"],
        criticality=result.get("criticality"),
        criticality_factor=result.get("criticality_factor"),
        policy_count=result.get("policy_count"),
        trend_7d=trend,
    )


@router.get("/urs", response_model=List[URSResponse])
def list_urs(
    limit: int = Query(200, le=1000),
    avs_mode: str = Query("hybrid", pattern="^(hybrid|avg|max)$"),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """URS for every asset, sorted highest-risk first."""
    assets = db.query(Asset).all()
    rows: List[URSResponse] = []
    for a in assets:
        r = compute_urs(db, a.id, avs_mode=avs_mode, persist_snapshot=False)
        rows.append(URSResponse(
            asset_id=a.id,
            hostname=a.hostname,
            avs=r["avs"],
            ass=r["ass"],
            urs=r["urs"],
            severity=r["severity"],
            criticality=r.get("criticality"),
            criticality_factor=r.get("criticality_factor"),
            policy_count=r.get("policy_count"),
        ))
    rows.sort(key=lambda x: (x.urs is None, -(x.urs or 0)))
    return rows[:limit]


@router.post("/urs/recompute-all")
def recompute_all_urs(
    avs_mode: str = Query("hybrid", pattern="^(hybrid|avg|max)$"),
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """Force a URS recompute across every asset and snapshot today."""
    stats = compute_urs_for_all(db, avs_mode=avs_mode)
    db.commit()
    return stats


@router.post("/refresh")
def refresh_everything(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """
    Trigger an SCA refresh for every Wazuh-linked asset.

    Synchronous — typical run is a few seconds per asset since SCA data
    is small (one summary GET per agent). For very large fleets consider
    moving this onto the same background-job pattern as the Vulnrichment
    correction.
    """
    return refresh_all_compliance(db)
