"""Pre-Deployment Enforcement API — G4.

CI/CD gate: checks if a deployment is safe by evaluating the compliance
status of all affected controls. Blocks deploys with non-compliant controls.
"""

import json
import logging
import uuid
from typing import Optional

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import text

from db.session import SessionLocal

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/v1/deployment-checks", tags=["deployment-checks"])

SEVERITY_WEIGHT = {
    "critical": 4.0,
    "high": 3.0,
    "medium": 2.0,
    "low": 1.0,
}


class DeployCheckRequest(BaseModel):
    tenant_id: str
    commit_hash: str
    branch: Optional[str] = None
    environment: str = "production"
    affected_control_ids: list[str] = []
    metadata: dict = {}


class OverrideRequest(BaseModel):
    override_by: str
    override_reason: str


@router.post("")
async def check_deployment(req: DeployCheckRequest):
    """Check if a deployment is safe. Returns verdict: approved/blocked."""
    db = SessionLocal()
    try:
        check_id = str(uuid.uuid4())

        blocking = []
        warnings = []
        risk_score = 0.0

        if req.affected_control_ids:
            # Look up latest decision status for each affected control
            for ctrl_id in req.affected_control_ids:
                row = db.execute(text("""
                    SELECT dt.status, dt.decision_reason, dt.fix_strategy,
                           cc.control_id, cc.title, cc.severity
                    FROM decision_traces dt
                    JOIN canonical_controls cc ON cc.id = dt.control_uuid
                    WHERE cc.control_id = :cid
                    ORDER BY dt.decided_at DESC NULLS LAST
                    LIMIT 1
                """), {"cid": ctrl_id}).fetchone()

                if not row:
                    # No decision → treat as not_assessed (warning)
                    warnings.append({
                        "control_id": ctrl_id,
                        "status": "not_assessed",
                        "reason": "No compliance decision recorded",
                    })
                    continue

                status = row[0]
                severity = row[5] or "medium"
                weight = SEVERITY_WEIGHT.get(severity, 2.0)

                if status in ("not_compliant", "under_remediation"):
                    blocking.append({
                        "control_id": row[3],
                        "title": row[4],
                        "status": status,
                        "reason": row[1],
                        "fix_strategy": row[2],
                        "severity": severity,
                    })
                    risk_score += weight
                elif status == "partially_compliant":
                    warnings.append({
                        "control_id": row[3],
                        "title": row[4],
                        "status": status,
                        "reason": row[1],
                        "severity": severity,
                    })
                    risk_score += weight * 0.5

        # Also check for open failure events (G3)
        if req.affected_control_ids:
            placeholders = ",".join(["'%s'" % c for c in req.affected_control_ids])
            open_failures = db.execute(text(f"""
                SELECT cc.control_id, de.summary
                FROM decision_events de
                JOIN canonical_controls cc ON cc.id = de.control_uuid
                WHERE cc.control_id IN ({placeholders})
                  AND de.event_type = 'failure'
                  AND de.created_at > NOW() - interval '30 days'
                  AND NOT EXISTS (
                      SELECT 1 FROM decision_events de2
                      WHERE de2.control_uuid = de.control_uuid
                        AND de2.event_type = 'verification'
                        AND de2.created_at > de.created_at
                  )
            """)).fetchall()

            for f in open_failures:
                if not any(b["control_id"] == f[0] for b in blocking):
                    blocking.append({
                        "control_id": f[0],
                        "status": "open_failure",
                        "reason": f[1] or "Unresolved failure event",
                        "severity": "high",
                    })
                    risk_score += 3.0

        verdict = "approved" if not blocking else "blocked"
        summary = (
            f"{len(blocking)} blocking, {len(warnings)} warnings. "
            + ("Deploy approved." if verdict == "approved"
               else f"Fix {', '.join(b['control_id'] for b in blocking)} before deploying.")
        )

        # Store check result
        db.execute(text("""
            INSERT INTO deployment_checks
                (id, tenant_id, commit_hash, branch, environment,
                 verdict, affected_control_ids, blocking_controls,
                 warning_controls, risk_score, summary, metadata)
            VALUES
                (CAST(:id AS uuid), CAST(:tid AS uuid), :hash, :branch, :env,
                 :verdict, CAST(:affected AS jsonb), CAST(:blocking AS jsonb),
                 CAST(:warnings AS jsonb), :risk, :summary, CAST(:meta AS jsonb))
        """), {
            "id": check_id,
            "tid": req.tenant_id,
            "hash": req.commit_hash,
            "branch": req.branch,
            "env": req.environment,
            "verdict": verdict,
            "affected": json.dumps(req.affected_control_ids),
            "blocking": json.dumps(blocking),
            "warnings": json.dumps(warnings),
            "risk": risk_score,
            "summary": summary,
            "meta": json.dumps(req.metadata),
        })
        db.commit()

        return {
            "id": check_id,
            "verdict": verdict,
            "risk_score": risk_score,
            "blocking_controls": blocking,
            "warning_controls": warnings,
            "summary": summary,
        }
    finally:
        db.close()


@router.get("/stats")
async def check_stats(tenant_id: Optional[str] = None):
    """Deployment check statistics."""
    db = SessionLocal()
    try:
        tf = ""
        params: dict = {}
        if tenant_id:
            tf = "WHERE tenant_id = CAST(:tid AS uuid)"
            params["tid"] = tenant_id

        by_verdict = db.execute(text(f"""
            SELECT verdict, count(*) FROM deployment_checks {tf}
            GROUP BY verdict
        """), params).fetchall()

        total = sum(r[1] for r in by_verdict)
        verdicts = {r[0]: r[1] for r in by_verdict}

        return {
            "total_checks": total,
            "by_verdict": verdicts,
            "approval_rate": round(
                verdicts.get("approved", 0) / total * 100, 1
            ) if total > 0 else 0,
            "override_count": verdicts.get("override", 0),
        }
    finally:
        db.close()


@router.post("/{check_id}/override")
async def override_check(check_id: str, req: OverrideRequest):
    """Override a blocked deployment (with justification)."""
    db = SessionLocal()
    try:
        result = db.execute(text("""
            UPDATE deployment_checks
            SET verdict = 'override', override_by = :by, override_reason = :reason
            WHERE id = CAST(:id AS uuid) AND verdict = 'blocked'
        """), {
            "id": check_id,
            "by": req.override_by,
            "reason": req.override_reason,
        })
        db.commit()

        if result.rowcount == 0:
            raise HTTPException(status_code=404, detail="Check not found or not blocked")

        return {"id": check_id, "verdict": "override", "override_by": req.override_by}
    finally:
        db.close()


@router.get("/{check_id}")
async def get_check(check_id: str):
    """Get details of a deployment check."""
    db = SessionLocal()
    try:
        row = db.execute(text("""
            SELECT * FROM deployment_checks WHERE id = CAST(:id AS uuid)
        """), {"id": check_id}).fetchone()

        if not row:
            raise HTTPException(status_code=404, detail="Check not found")

        return {
            "id": str(row.id),
            "tenant_id": str(row.tenant_id),
            "commit_hash": row.commit_hash,
            "branch": row.branch,
            "environment": row.environment,
            "verdict": row.verdict,
            "affected_control_ids": row.affected_control_ids,
            "blocking_controls": row.blocking_controls,
            "warning_controls": row.warning_controls,
            "risk_score": float(row.risk_score),
            "override_by": row.override_by,
            "override_reason": row.override_reason,
            "summary": row.summary,
            "created_at": str(row.created_at),
        }
    finally:
        db.close()
