"""POST /governance/authorize/ — GSAP §5.2"""
import logging
import secrets, uuid
from datetime import datetime, timedelta, UTC
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlmodel.ext.asyncio.session import AsyncSession
from gsap_broker.db import get_session
from gsap_broker.db_models import AuthorizationContextDB
from gsap_broker.drivers.registry import DriverRegistry
from gsap_broker.models import (
    AuthorizeRequest, AuthorizeResponse, AuthorizationContext,
    Principal, Accord, Operation, IdentityProof)
from gsap_broker.settings import settings
from gsap_broker import chronicle

logger = logging.getLogger(__name__)

# Accord templates that require device compliance.
# In production these would come from a database or config file.
_ACCORD_COMPLIANCE = {
    "infrastructure-operations": {"device_compliance_required": True},
    "device-management": {"device_compliance_required": True},
}

router = APIRouter()


def _extract_token_data(http_request: Request) -> dict:
    """Extract and decode JWT from Authorization header (unverified — driver validates)."""
    auth = http_request.headers.get("authorization", "")
    if not auth.startswith("Bearer "):
        return {}
    token = auth[7:]
    try:
        import base64, json
        payload = token.split(".")[1]
        payload += "=" * (4 - len(payload) % 4)
        return json.loads(base64.urlsafe_b64decode(payload))
    except Exception:
        return {}


@router.post("/authorize/", response_model=AuthorizeResponse, summary="Issue AC (GSAP §5.2)")
async def authorize(body: AuthorizeRequest, http_request: Request, db: AsyncSession = Depends(get_session)):
    request = body
    token_data = _extract_token_data(http_request)
    raw_token = ""
    auth_header = http_request.headers.get("authorization", "")
    if auth_header.startswith("Bearer "):
        raw_token = auth_header[7:]

    try:
        driver = DriverRegistry.get(request.driver_id, config={
            "requested_accord": request.accord_template,
            "domain": settings.keycloak_domain,
            "did_template": settings.keycloak_did_template,
            "elevated_suffix": settings.keycloak_elevated_role_suffix,
            "_token_data": token_data,
            "_raw_token": raw_token,
            "entra_tenant_id": settings.entra_tenant_id,
            "entra_client_id": settings.entra_client_id,
            # Fix C-1: Keycloak driver needs these for JWKS verification
            "keycloak_url": settings.keycloak_url,
            "keycloak_realm": settings.keycloak_realm,
            "keycloak_client_id": settings.keycloak_admin_client_id,
        })
    except KeyError as e:
        raise HTTPException(status_code=400, detail=str(e))

    auth_result = await driver.authenticate()

    if auth_result.needs_elevation:
        poll_token = secrets.token_urlsafe(32)
        ac_db = AuthorizationContextDB(
            principal_did="", driver_id=request.driver_id, playbook=request.playbook,
            corpus_entry_cid=request.corpus_entry_cid, parameters_cid=request.parameters_cid,
            accord_template=request.accord_template, status="pending",
            expires_at=datetime.now(UTC) + timedelta(minutes=10), poll_token=poll_token)
        db.add(ac_db); await db.commit()
        return AuthorizeResponse(
            status="pending_elevation", poll_token=poll_token,
            elevation_instructions=auth_result.elevation_required.instructions,
            activation_url=auth_result.elevation_required.activation_url)

    if not auth_result.is_authorized:
        raise HTTPException(status_code=403, detail=auth_result.denial_reason)

    # ── Compliance gate ──────────────────────────────────────────
    device_id: Optional[str] = auth_result.device_id
    device_compliant: Optional[bool] = None
    compliance_checked_at: Optional[datetime] = None

    if settings.intune_enabled:
        accord_policy = _ACCORD_COMPLIANCE.get(request.accord_template, {})
        compliance_required = accord_policy.get(
            "device_compliance_required", settings.intune_compliance_required
        )

        if compliance_required:
            if not device_id:
                if settings.intune_compliance_strict:
                    await chronicle.emit("DEVICE_COMPLIANCE_CHECKED", {
                        "event_code": "0x2801",
                        "principal_did": auth_result.principal_did,
                        "accord_template": request.accord_template,
                        "device_id": None,
                        "decision": "denied",
                        "reason": "no_device_identity",
                    })
                    raise HTTPException(
                        status_code=403,
                        detail="Device identity required for this accord template.",
                    )
                # Permissive mode: allow without compliance fields
                logger.info("Compliance required but no device_id — permissive mode, allowing")
            else:
                # Check compliance via Intune connector cache/API
                compliance_state = await _check_device_compliance(device_id)
                compliance_checked_at = datetime.now(UTC)
                device_compliant = compliance_state

                await chronicle.emit("DEVICE_COMPLIANCE_CHECKED", {
                    "event_code": "0x2801",
                    "principal_did": auth_result.principal_did,
                    "accord_template": request.accord_template,
                    "device_id": device_id,
                    "compliant": compliance_state,
                    "decision": "allowed" if compliance_state else "denied",
                })

                if not compliance_state:
                    raise HTTPException(
                        status_code=403,
                        detail="Device is not compliant. AC issuance denied.",
                    )
    # ── End compliance gate ───────────────────────────────────────

    now = datetime.now(UTC)
    expires = now + timedelta(minutes=settings.ac_ttl_minutes)
    ctx_id = uuid.uuid4()

    # Fix C-2: on_behalf_of requires gsap:impersonate role
    if request.on_behalf_of:
        caller_roles = getattr(auth_result, "elevation_active", [])
        # Check for impersonation role in JWT roles (passed through auth_result)
        token_roles = token_data.get("roles", []) + token_data.get("realm_access", {}).get("roles", [])
        if "gsap:impersonate" not in token_roles:
            raise HTTPException(
                status_code=403,
                detail="on_behalf_of requires gsap:impersonate role.",
            )

    principal_did = request.on_behalf_of or auth_result.principal_did
    display_name = request.on_behalf_of.rsplit("/", 1)[-1] if request.on_behalf_of else auth_result.display_name

    ac = AuthorizationContext(
        context_id=ctx_id, issued_at=now, expires_at=expires,
        principal=Principal(did=principal_did, display_name=display_name, driver_id=request.driver_id),
        accord=Accord(template=request.accord_template),
        operation=Operation(playbook=request.playbook, corpus_entry_cid=request.corpus_entry_cid, parameters_cid=request.parameters_cid),
        identity_proof=IdentityProof(token_jti=auth_result.token_jti, elevation_active=auth_result.elevation_active, mfa_satisfied=auth_result.mfa_satisfied),
        broker={"did": settings.broker_did, "name": settings.broker_name},
        device_id=device_id, device_compliant=device_compliant,
        compliance_checked_at=compliance_checked_at)

    ac_db = AuthorizationContextDB(
        context_id=ctx_id, principal_did=principal_did, driver_id=request.driver_id,
        playbook=request.playbook, corpus_entry_cid=request.corpus_entry_cid,
        parameters_cid=request.parameters_cid, accord_template=request.accord_template,
        token_jti=auth_result.token_jti, elevation_active=auth_result.elevation_active,
        mfa_satisfied=auth_result.mfa_satisfied, status="authorized", issued_at=now, expires_at=expires,
        session_mode=request.session_mode)
    db.add(ac_db)
    cid = await chronicle.emit("GSAP_AC_ISSUED", {"event_code": "0x2704", "context_id": str(ctx_id), "principal_did": principal_did, "playbook": request.playbook})
    ac_db.chronicle_event_cid = cid
    await db.commit()

    return AuthorizeResponse(status="authorized", authorization_context=ac)

@router.get("/authorize/{poll_token}/", response_model=AuthorizeResponse, summary="Poll (GSAP §5.3)")
async def authorize_poll(poll_token: str, db: AsyncSession = Depends(get_session)):
    from sqlmodel import select
    result = await db.exec(select(AuthorizationContextDB).where(AuthorizationContextDB.poll_token == poll_token))
    ac_db = result.first()
    if not ac_db: raise HTTPException(status_code=404, detail="Not found.")
    return AuthorizeResponse(status=ac_db.status, poll_token=poll_token)


async def _check_device_compliance(device_id: str) -> bool:
    """Check device compliance via the Intune connector cache or Graph API.

    Returns True if compliant, False otherwise.
    """
    try:
        from gsap_broker.routers.connectors import _registry
        intune = _registry.get("intune")
        if intune is None:
            logger.warning("Intune connector not registered — defaulting to compliant")
            return True
        from gsap_broker.connectors.base import ConnectorContext
        ctx = ConnectorContext(gsap_context_id="compliance-gate")
        result = await intune.invoke("get_compliance", {"device_id": device_id}, ctx)
        if result.success and result.data:
            return result.data.get("compliant", False)
        return False
    except Exception as e:
        logger.error("Compliance check failed: %s", e)
        return False
