"""REST + SSE endpoints for penetration testing orchestration.

All mutation endpoints (create, authorize, control) require an authenticated
Firebase principal. Read-only endpoints (get, list, tools, findings) allow
service principals for monitoring.
"""

from __future__ import annotations

import asyncio
import json
import logging
from typing import Any

from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import StreamingResponse

from dsc_api.models.pentest_models import (
    CreatePentestRequest,
    GenerateReportRequest,
    PentestAuthorization,
    PentestControlRequest,
    PentestExecutionMode,
    PentestReport,
    PentestSession,
    PentestSummary,
    ScopeValidationResult,
    ValidateScopeRequest,
)
from dsc_api.services import pentest_orchestrator
from dsc_api.services.auth_context import get_current_principal

logger = logging.getLogger(__name__)

router = APIRouter()


# ---------------------------------------------------------------------------
# Auth helper
# ---------------------------------------------------------------------------


def _require_auth(request: Request) -> None:
    """Require an authenticated user principal for write operations."""
    principal = get_current_principal()
    if principal is None:
        raise HTTPException(status_code=401, detail="Authentication required for pentest operations")
    if not principal.bearer_token:
        raise HTTPException(status_code=401, detail="Valid authentication token required")


def _require_auth_read(request: Request) -> None:
    """Require at least a service principal for read operations."""
    principal = get_current_principal()
    if principal is None:
        raise HTTPException(status_code=401, detail="Authentication required")


# ---------------------------------------------------------------------------
# POST /api/pentest — create a new session
# ---------------------------------------------------------------------------


@router.post("", response_model=PentestSession)
async def create_session(request: Request, body: CreatePentestRequest) -> PentestSession:
    """Create a new pentest session in Draft status.

    Requires authenticated user. Enforces concurrent session limits.
    """
    _require_auth(request)
    try:
        return pentest_orchestrator.create_session(body.config)
    except ValueError as exc:
        raise HTTPException(status_code=429, detail=str(exc))


# ---------------------------------------------------------------------------
# POST /api/pentest/validate-scope — pre-validate scope
# ---------------------------------------------------------------------------


@router.post("/validate-scope", response_model=ScopeValidationResult)
async def validate_scope(request: Request, body: ValidateScopeRequest) -> ScopeValidationResult:
    """Validate scope before authorization (lightweight pre-check)."""
    _require_auth(request)
    return await pentest_orchestrator.validate_scope(body.scope, body.allow_private)


# ---------------------------------------------------------------------------
# GET /api/pentest — list all sessions
# ---------------------------------------------------------------------------


@router.get("", response_model=list[PentestSession])
async def list_sessions(
    request: Request,
    status: str | None = Query(None, description="Filter by session status"),
) -> list[PentestSession]:
    """List all pentest sessions, optionally filtered by status."""
    _require_auth_read(request)
    return pentest_orchestrator.list_sessions(status_filter=status)


# ---------------------------------------------------------------------------
# GET /api/pentest/tools — check tool availability
# ---------------------------------------------------------------------------


@router.get("/tools")
async def check_tools(request: Request) -> dict[str, bool]:
    """Check which pentest tools are installed on the system."""
    _require_auth_read(request)
    return pentest_orchestrator.check_tool_availability()


# ---------------------------------------------------------------------------
# POST /api/pentest/cleanup — remove stale sessions
# ---------------------------------------------------------------------------


@router.post("/cleanup")
async def cleanup_sessions(request: Request) -> dict[str, int]:
    """Remove completed/failed/cancelled sessions older than 24 hours."""
    _require_auth(request)
    removed = pentest_orchestrator.cleanup_stale_sessions()
    return {"removed": removed}


# ---------------------------------------------------------------------------
# GET /api/pentest/{session_id} — retrieve session state
# ---------------------------------------------------------------------------


@router.get("/{session_id}", response_model=PentestSession)
async def get_session(request: Request, session_id: str) -> PentestSession:
    """Retrieve the current state of a pentest session."""
    _require_auth_read(request)
    session = pentest_orchestrator.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Pentest session not found")
    return session


# ---------------------------------------------------------------------------
# POST /api/pentest/{session_id}/authorize — authorize and start
# ---------------------------------------------------------------------------


@router.post("/{session_id}/authorize", response_model=PentestSession)
async def authorize_and_start(
    request: Request,
    session_id: str,
    auth: PentestAuthorization,
) -> PentestSession:
    """Authorize a Draft session and start execution.

    This is the critical gate — the user must confirm target ownership
    and the scope hash must match (tamper detection).
    """
    _require_auth(request)
    try:
        return await pentest_orchestrator.authorize_and_start(session_id, auth)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    except PermissionError as exc:
        raise HTTPException(status_code=403, detail=str(exc))


# ---------------------------------------------------------------------------
# POST /api/pentest/{session_id}/control — pause/resume/cancel
# ---------------------------------------------------------------------------


@router.post("/{session_id}/control", response_model=PentestSession)
async def control_session(
    request: Request,
    session_id: str,
    body: PentestControlRequest,
) -> PentestSession:
    """Control a pentest session: pause, resume, or cancel."""
    _require_auth(request)
    try:
        return await pentest_orchestrator.control_session(session_id, body.action)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))


# ---------------------------------------------------------------------------
# GET /api/pentest/{session_id}/findings — get findings
# ---------------------------------------------------------------------------


@router.get("/{session_id}/findings")
async def get_findings(request: Request, session_id: str) -> list[dict[str, Any]]:
    """Get all findings for a pentest session."""
    _require_auth_read(request)
    session = pentest_orchestrator.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Pentest session not found")
    return [f.model_dump() for f in session.findings]


# ---------------------------------------------------------------------------
# GET /api/pentest/{session_id}/attack-surface — get CPG analysis results
# ---------------------------------------------------------------------------


@router.get("/{session_id}/attack-surface")
async def get_attack_surface(request: Request, session_id: str) -> dict[str, Any]:
    """Get the CPG attack surface analysis for a session."""
    _require_auth_read(request)
    surface = pentest_orchestrator.get_attack_surface(session_id)
    if surface is None:
        return {"available": False, "message": "Attack surface not yet computed"}
    return {
        "available": True,
        "total_entry_points": surface.total_entry_points,
        "total_attack_vectors": surface.total_attack_vectors,
        "risk_summary": surface.risk_summary,
    }


# ---------------------------------------------------------------------------
# GET /api/pentest/{session_id}/poc — get PoC report
# ---------------------------------------------------------------------------


@router.get("/{session_id}/poc")
async def get_poc_report(request: Request, session_id: str) -> dict[str, Any]:
    """Get the PoC report for a session."""
    _require_auth_read(request)
    report = pentest_orchestrator.get_poc_report(session_id)
    if report is None:
        return {"available": False, "message": "PoC report not yet generated"}
    return {
        "available": True,
        "total_proven": report.total_proven,
        "total_attempted": report.total_attempted,
        "summary": report.summary,
        "pocs": [report._serialize_poc(p) for p in report.pocs[:50]],  # Cap at 50 PoCs
    }


# ---------------------------------------------------------------------------
# POST /api/pentest/{session_id}/report — generate report
# ---------------------------------------------------------------------------


@router.post("/{session_id}/report", response_model=PentestReport)
async def generate_report(
    request: Request,
    session_id: str,
    body: GenerateReportRequest,
) -> PentestReport:
    """Generate a structured pentest report."""
    _require_auth(request)
    session = pentest_orchestrator.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Pentest session not found")

    from datetime import datetime, timezone

    return PentestReport(
        session_id=session_id,
        generated_at=datetime.now(timezone.utc).isoformat(),
        summary=session.summary or PentestSummary(),
        findings=session.findings,
        format=body.format,
    )


# ---------------------------------------------------------------------------
# GET /api/pentest/{session_id}/events — SSE event stream
# ---------------------------------------------------------------------------


@router.get("/{session_id}/events")
async def session_events(request: Request, session_id: str) -> StreamingResponse:
    """Stream real-time pentest events as Server-Sent Events."""
    _require_auth_read(request)
    session = pentest_orchestrator.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Pentest session not found")

    queue = pentest_orchestrator.subscribe(session_id)

    async def event_generator():
        try:
            while True:
                event = await asyncio.wait_for(queue.get(), timeout=300)
                if event is None:
                    yield f"data: {json.dumps({'type': 'done'})}\n\n"
                    break
                payload: dict[str, Any] = {
                    "type": event.event.value,
                    "session_id": event.session_id,
                    "timestamp": event.timestamp,
                    **event.payload,
                }
                yield f"data: {json.dumps(payload)}\n\n"
        except asyncio.TimeoutError:
            yield f"data: {json.dumps({'type': 'timeout'})}\n\n"
        except asyncio.CancelledError:
            pass
        finally:
            pentest_orchestrator.unsubscribe(session_id, queue)

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        },
    )
