"""
FastAPI routes for Change-Request System.

Endpoints:
  GET    /change-requests              — List (filter: status, doc_type, priority)
  GET    /change-requests/stats        — Summary counts
  GET    /change-requests/{id}         — Detail + audit log
  POST   /change-requests              — Create manually
  POST   /change-requests/{id}/accept  — Accept → create new version
  POST   /change-requests/{id}/reject  — Reject with reason
  POST   /change-requests/{id}/edit    — Edit proposal, then accept
  DELETE /change-requests/{id}         — Soft-delete
"""

import json
import logging
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Header
from pydantic import BaseModel
from sqlalchemy import text
from sqlalchemy.orm import Session

from classroom_engine.database import get_db
from .tenant_utils import get_tenant_id

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/change-requests", tags=["change-requests"])

VALID_STATUSES = {"pending", "accepted", "rejected", "edited_and_accepted"}
VALID_PRIORITIES = {"low", "normal", "high", "critical"}
VALID_DOC_TYPES = {"dsfa", "vvt", "tom", "loeschfristen", "obligation"}


# =============================================================================
# Pydantic Schemas
# =============================================================================

class ChangeRequestCreate(BaseModel):
    trigger_type: str = "manual"
    trigger_source_id: Optional[str] = None
    target_document_type: str
    target_document_id: Optional[str] = None
    target_section: Optional[str] = None
    proposal_title: str
    proposal_body: Optional[str] = None
    proposed_changes: dict = {}
    priority: str = "normal"


class ChangeRequestEdit(BaseModel):
    proposal_body: Optional[str] = None
    proposed_changes: Optional[dict] = None


class ChangeRequestReject(BaseModel):
    rejection_reason: str


# =============================================================================
# Helpers
# =============================================================================

def _cr_to_dict(row) -> dict:
    return {
        "id": str(row["id"]),
        "tenant_id": row["tenant_id"],
        "trigger_type": row["trigger_type"],
        "trigger_source_id": str(row["trigger_source_id"]) if row["trigger_source_id"] else None,
        "target_document_type": row["target_document_type"],
        "target_document_id": str(row["target_document_id"]) if row["target_document_id"] else None,
        "target_section": row["target_section"],
        "proposal_title": row["proposal_title"],
        "proposal_body": row["proposal_body"],
        "proposed_changes": row["proposed_changes"] or {},
        "status": row["status"],
        "priority": row["priority"],
        "decided_by": row["decided_by"],
        "decided_at": row["decided_at"].isoformat() if row["decided_at"] else None,
        "rejection_reason": row["rejection_reason"],
        "resulting_version_id": str(row["resulting_version_id"]) if row["resulting_version_id"] else None,
        "created_by": row["created_by"],
        "created_at": row["created_at"].isoformat() if row["created_at"] else None,
        "updated_at": row["updated_at"].isoformat() if row["updated_at"] else None,
    }


def _log_cr_audit(db, cr_id, tenant_id, action, performed_by="system", before_state=None, after_state=None):
    db.execute(
        text("""
            INSERT INTO compliance_change_request_audit
                (change_request_id, tenant_id, action, performed_by, before_state, after_state)
            VALUES (:cr_id, :tid, :action, :by, CAST(:before AS jsonb), CAST(:after AS jsonb))
        """),
        {
            "cr_id": cr_id,
            "tid": tenant_id,
            "action": action,
            "by": performed_by,
            "before": json.dumps(before_state) if before_state else None,
            "after": json.dumps(after_state) if after_state else None,
        },
    )


# =============================================================================
# Routes
# =============================================================================

@router.get("")
async def list_change_requests(
    status: Optional[str] = Query(None),
    target_document_type: Optional[str] = Query(None),
    priority: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(100, ge=1, le=500),
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
):
    """List change requests with optional filters."""
    sql = "SELECT * FROM compliance_change_requests WHERE tenant_id = :tid AND NOT is_deleted"
    params = {"tid": tid}

    if status:
        sql += " AND status = :status"
        params["status"] = status
    if target_document_type:
        sql += " AND target_document_type = :doc_type"
        params["doc_type"] = target_document_type
    if priority:
        sql += " AND priority = :priority"
        params["priority"] = priority

    sql += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END, created_at DESC"
    sql += " LIMIT :limit OFFSET :skip"
    params["limit"] = limit
    params["skip"] = skip

    rows = db.execute(text(sql), params).fetchall()
    return [_cr_to_dict(r) for r in rows]


@router.get("/stats")
async def get_stats(
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
):
    """Summary counts for change requests."""
    rows = db.execute(
        text("""
            SELECT
                COUNT(*) FILTER (WHERE status = 'pending') AS total_pending,
                COUNT(*) FILTER (WHERE status = 'pending' AND priority = 'critical') AS critical_count,
                COUNT(*) FILTER (WHERE status = 'accepted' OR status = 'edited_and_accepted') AS total_accepted,
                COUNT(*) FILTER (WHERE status = 'rejected') AS total_rejected
            FROM compliance_change_requests
            WHERE tenant_id = :tid AND NOT is_deleted
        """),
        {"tid": tid},
    ).fetchone()

    # By document type
    doc_type_rows = db.execute(
        text("""
            SELECT target_document_type, COUNT(*)
            FROM compliance_change_requests
            WHERE tenant_id = :tid AND status = 'pending' AND NOT is_deleted
            GROUP BY target_document_type
        """),
        {"tid": tid},
    ).fetchall()

    return {
        "total_pending": rows[0] or 0,
        "critical_count": rows[1] or 0,
        "total_accepted": rows[2] or 0,
        "total_rejected": rows[3] or 0,
        "by_document_type": {r[0]: r[1] for r in doc_type_rows},
    }


@router.get("/{cr_id}")
async def get_change_request(
    cr_id: str,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
):
    """Get change request detail with audit log."""
    row = db.execute(
        text("SELECT * FROM compliance_change_requests WHERE id = :id AND tenant_id = :tid AND NOT is_deleted"),
        {"id": cr_id, "tid": tid},
    ).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Change request not found")

    cr = _cr_to_dict(row)

    # Attach audit log
    audit_rows = db.execute(
        text("""
            SELECT id, action, performed_by, before_state, after_state, created_at
            FROM compliance_change_request_audit
            WHERE change_request_id = :cr_id
            ORDER BY created_at DESC
        """),
        {"cr_id": cr_id},
    ).fetchall()

    cr["audit_log"] = [
        {
            "id": str(a[0]),
            "action": a[1],
            "performed_by": a[2],
            "before_state": a[3],
            "after_state": a[4],
            "created_at": a[5].isoformat() if a[5] else None,
        }
        for a in audit_rows
    ]
    return cr


@router.post("", status_code=201)
async def create_change_request(
    body: ChangeRequestCreate,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
    x_user_id: Optional[str] = Header(None, alias="X-User-ID"),
):
    """Create a change request manually."""
    if body.target_document_type not in VALID_DOC_TYPES:
        raise HTTPException(status_code=400, detail=f"Invalid target_document_type: {body.target_document_type}")
    if body.priority not in VALID_PRIORITIES:
        raise HTTPException(status_code=400, detail=f"Invalid priority: {body.priority}")

    row = db.execute(
        text("""
            INSERT INTO compliance_change_requests
                (tenant_id, trigger_type, trigger_source_id, target_document_type,
                 target_document_id, target_section, proposal_title, proposal_body,
                 proposed_changes, priority, created_by)
            VALUES (:tid, :trigger_type, :trigger_source_id, :doc_type,
                    :doc_id, :section, :title, :body,
                    CAST(:changes AS jsonb), :priority, :created_by)
            RETURNING *
        """),
        {
            "tid": tid,
            "trigger_type": body.trigger_type,
            "trigger_source_id": body.trigger_source_id,
            "doc_type": body.target_document_type,
            "doc_id": body.target_document_id,
            "section": body.target_section,
            "title": body.proposal_title,
            "body": body.proposal_body,
            "changes": json.dumps(body.proposed_changes),
            "priority": body.priority,
            "created_by": x_user_id or "system",
        },
    ).fetchone()

    _log_cr_audit(db, row["id"], tid, "CREATED", x_user_id or "system")
    db.commit()

    return _cr_to_dict(row)


@router.post("/{cr_id}/accept")
async def accept_change_request(
    cr_id: str,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
    x_user_id: Optional[str] = Header(None, alias="X-User-ID"),
):
    """Accept a change request → creates a new document version."""
    row = db.execute(
        text("SELECT * FROM compliance_change_requests WHERE id = :id AND tenant_id = :tid AND NOT is_deleted"),
        {"id": cr_id, "tid": tid},
    ).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Change request not found")
    if row["status"] != "pending":
        raise HTTPException(status_code=422, detail=f"Cannot accept CR in status '{row['status']}'")

    user = x_user_id or "system"
    before_state = {"status": row["status"]}

    # If there's a target document, create a version snapshot
    resulting_version_id = None
    if row["target_document_id"]:
        try:
            from .versioning_utils import create_version_snapshot
            version = create_version_snapshot(
                db,
                doc_type=row["target_document_type"],
                doc_id=str(row["target_document_id"]),
                tenant_id=tid,
                snapshot=row["proposed_changes"] or {},
                change_summary=f"Accepted CR: {row['proposal_title']}",
                created_by=user,
            )
            resulting_version_id = version["id"]
        except Exception as e:
            logger.warning(f"Could not create version for CR {cr_id}: {e}")

    # Update CR status
    updated = db.execute(
        text("""
            UPDATE compliance_change_requests
            SET status = 'accepted', decided_by = :user, decided_at = NOW(),
                resulting_version_id = :ver_id, updated_at = NOW()
            WHERE id = :id AND tenant_id = :tid
            RETURNING *
        """),
        {"id": cr_id, "tid": tid, "user": user, "ver_id": resulting_version_id},
    ).fetchone()

    _log_cr_audit(db, cr_id, tid, "ACCEPTED", user, before_state, {"status": "accepted"})
    db.commit()

    return _cr_to_dict(updated)


@router.post("/{cr_id}/reject")
async def reject_change_request(
    cr_id: str,
    body: ChangeRequestReject,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
    x_user_id: Optional[str] = Header(None, alias="X-User-ID"),
):
    """Reject a change request with reason."""
    row = db.execute(
        text("SELECT * FROM compliance_change_requests WHERE id = :id AND tenant_id = :tid AND NOT is_deleted"),
        {"id": cr_id, "tid": tid},
    ).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Change request not found")
    if row["status"] != "pending":
        raise HTTPException(status_code=422, detail=f"Cannot reject CR in status '{row['status']}'")

    user = x_user_id or "system"

    updated = db.execute(
        text("""
            UPDATE compliance_change_requests
            SET status = 'rejected', decided_by = :user, decided_at = NOW(),
                rejection_reason = :reason, updated_at = NOW()
            WHERE id = :id AND tenant_id = :tid
            RETURNING *
        """),
        {"id": cr_id, "tid": tid, "user": user, "reason": body.rejection_reason},
    ).fetchone()

    _log_cr_audit(db, cr_id, tid, "REJECTED", user, {"status": "pending"}, {"status": "rejected", "reason": body.rejection_reason})
    db.commit()

    return _cr_to_dict(updated)


@router.post("/{cr_id}/edit")
async def edit_change_request(
    cr_id: str,
    body: ChangeRequestEdit,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
    x_user_id: Optional[str] = Header(None, alias="X-User-ID"),
):
    """Edit the proposal, then auto-accept."""
    row = db.execute(
        text("SELECT * FROM compliance_change_requests WHERE id = :id AND tenant_id = :tid AND NOT is_deleted"),
        {"id": cr_id, "tid": tid},
    ).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Change request not found")
    if row["status"] != "pending":
        raise HTTPException(status_code=422, detail=f"Cannot edit CR in status '{row['status']}'")

    user = x_user_id or "system"
    updates = []
    params = {"id": cr_id, "tid": tid, "user": user}

    if body.proposal_body is not None:
        updates.append("proposal_body = :body")
        params["body"] = body.proposal_body
    if body.proposed_changes is not None:
        updates.append("proposed_changes = CAST(:changes AS jsonb)")
        params["changes"] = json.dumps(body.proposed_changes)

    updates.append("status = 'edited_and_accepted'")
    updates.append("decided_by = :user")
    updates.append("decided_at = NOW()")
    updates.append("updated_at = NOW()")

    sql = f"UPDATE compliance_change_requests SET {', '.join(updates)} WHERE id = :id AND tenant_id = :tid RETURNING *"
    updated = db.execute(text(sql), params).fetchone()

    _log_cr_audit(db, cr_id, tid, "EDITED_AND_ACCEPTED", user, {"status": "pending"}, {"status": "edited_and_accepted"})
    db.commit()

    return _cr_to_dict(updated)


@router.delete("/{cr_id}")
async def delete_change_request(
    cr_id: str,
    tid: str = Depends(get_tenant_id),
    db: Session = Depends(get_db),
    x_user_id: Optional[str] = Header(None, alias="X-User-ID"),
):
    """Soft-delete a change request."""
    result = db.execute(
        text("""
            UPDATE compliance_change_requests
            SET is_deleted = TRUE, updated_at = NOW()
            WHERE id = :id AND tenant_id = :tid AND NOT is_deleted
        """),
        {"id": cr_id, "tid": tid},
    )
    if result.rowcount == 0:
        raise HTTPException(status_code=404, detail="Change request not found")

    _log_cr_audit(db, cr_id, tid, "DELETED", x_user_id or "system")
    db.commit()

    return {"success": True, "message": "Change request deleted"}
