"""
Approvals Router — approval request lifecycle management.

Endpoints:
- POST /api/v1/approvals                — create request (from enforcement hook)
- GET  /api/v1/approvals                — list requests (with filters)
- GET  /api/v1/approvals/pending        — pending requests for current user
- GET  /api/v1/approvals/{id}           — get single request
- POST /api/v1/approvals/{id}/approve   — approve request
- POST /api/v1/approvals/{id}/reject    — reject request
"""

from fastapi import APIRouter, Depends, HTTPException, status, Request, Query
from typing import List, Optional
import structlog
from sqlalchemy.orm import Session

from control_plane_api.app.database import get_db
from control_plane_api.app.middleware.auth import get_current_organization
from control_plane_api.app.services.approval_service import ApprovalService
from control_plane_api.app.schemas.approval_request import (
    ApprovalRequestCreate,
    ApprovalRequestResponse,
    ApproveRequestBody,
    RejectRequestBody,
)

logger = structlog.get_logger()

router = APIRouter(tags=["Approvals"])


def get_approval_service(
    organization: dict = Depends(get_current_organization),
    db: Session = Depends(get_db),
) -> ApprovalService:
    return ApprovalService(organization_id=organization["id"], db=db)


def _request_to_response(req) -> ApprovalRequestResponse:
    return ApprovalRequestResponse(
        id=str(req.id),
        organization_id=req.organization_id,
        execution_id=req.execution_id,
        rule_id=str(req.rule_id) if req.rule_id else None,
        status=req.status,
        requester_email=req.requester_email,
        requester_id=req.requester_id,
        tool_name=req.tool_name,
        tool_args=req.tool_args,
        risk_level=req.risk_level,
        enforcement_context=req.enforcement_context,
        approver_emails=req.approver_emails,
        approved_by_email=req.approved_by_email,
        approved_by_name=req.approved_by_name,
        rejection_reason=req.rejection_reason,
        resolved_at=req.resolved_at.isoformat() if req.resolved_at else None,
        expires_at=req.expires_at.isoformat() if req.expires_at else None,
        created_at=req.created_at.isoformat() if req.created_at else None,
        updated_at=req.updated_at.isoformat() if req.updated_at else None,
    )


@router.post("", response_model=ApprovalRequestResponse, status_code=status.HTTP_201_CREATED)
def create_approval_request(
    body: ApprovalRequestCreate,
    request: Request,
    service: ApprovalService = Depends(get_approval_service),
):
    """Create an approval request (called by enforcement hook when a tool is blocked)."""
    requester_email = getattr(request.state, "user_email", None)
    requester_id = getattr(request.state, "user_id", None)

    approval = service.create_request(
        execution_id=body.execution_id,
        tool_name=body.tool_name,
        requester_email=requester_email,
        requester_id=requester_id,
        rule_id=body.rule_id,
        tool_args=body.tool_args,
        risk_level=body.risk_level,
        enforcement_context=body.enforcement_context,
        approver_emails=body.approver_emails,
        timeout_minutes=body.timeout_minutes,
    )
    return _request_to_response(approval)


@router.get("/pending", response_model=List[ApprovalRequestResponse])
def list_pending_approvals(
    request: Request,
    service: ApprovalService = Depends(get_approval_service),
    organization: dict = Depends(get_current_organization),
):
    """List pending approval requests where the current user is an authorized approver.

    Checks both direct email match AND group membership (since in Kubiya,
    approvers can be specified as group members via approver_type='group').
    """
    user_email = organization.get("user_email")
    if not user_email:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User email not available",
        )
    # User's Kubiya group names (e.g. ["Admin", "Users", "DevOps"])
    user_group_names = organization.get("user_group_names") or []
    approvals = service.list_pending_for_approver(user_email, user_group_names=user_group_names)
    return [_request_to_response(a) for a in approvals]


@router.get("", response_model=List[ApprovalRequestResponse])
def list_approval_requests(
    status_filter: Optional[str] = Query(None, alias="status"),
    requester_email: Optional[str] = None,
    execution_id: Optional[str] = None,
    limit: int = Query(50, ge=1, le=200),
    offset: int = Query(0, ge=0),
    service: ApprovalService = Depends(get_approval_service),
):
    """List approval requests with optional filters."""
    approvals = service.list_requests(
        status=status_filter,
        requester_email=requester_email,
        execution_id=execution_id,
        limit=limit,
        offset=offset,
    )
    return [_request_to_response(a) for a in approvals]


@router.get("/{request_id}", response_model=ApprovalRequestResponse)
def get_approval_request(
    request_id: str,
    service: ApprovalService = Depends(get_approval_service),
):
    """Get a single approval request by ID."""
    approval = service.get_request(request_id)
    if not approval:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Approval request not found")
    return _request_to_response(approval)


@router.post("/{request_id}/approve", response_model=ApprovalRequestResponse)
def approve_request(
    request_id: str,
    body: ApproveRequestBody,
    request: Request,
    service: ApprovalService = Depends(get_approval_service),
    organization: dict = Depends(get_current_organization),
):
    """
    Approve a pending request.

    The current user must be an authorized approver (by email or group membership).
    On success, publishes a WebSocket event to resume the blocked execution.
    """
    approver_email = organization.get("user_email")
    approver_name = organization.get("user_name")
    user_group_names = organization.get("user_group_names") or []

    if not approver_email:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User email not available",
        )

    approval = service.approve_request(
        request_id=request_id,
        approver_email=approver_email,
        approver_name=approver_name,
        comment=body.comment,
        user_group_names=user_group_names,
    )

    if not approval:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Request not found, already resolved, or you are not an authorized approver",
        )

    # Publish WebSocket event so the blocked session resumes
    _publish_approval_event(approval, "approved")

    return _request_to_response(approval)


@router.post("/{request_id}/reject", response_model=ApprovalRequestResponse)
def reject_request(
    request_id: str,
    body: RejectRequestBody,
    request: Request,
    service: ApprovalService = Depends(get_approval_service),
    organization: dict = Depends(get_current_organization),
):
    """
    Reject a pending request.

    The current user must be an authorized approver (by email or group membership).
    On rejection, the blocked tool execution receives an error.
    """
    rejector_email = organization.get("user_email")
    rejector_name = organization.get("user_name")
    user_group_names = organization.get("user_group_names") or []

    if not rejector_email:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User email not available",
        )

    approval = service.reject_request(
        request_id=request_id,
        rejector_email=rejector_email,
        rejector_name=rejector_name,
        reason=body.reason,
        user_group_names=user_group_names,
    )

    if not approval:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Request not found, already resolved, or you are not an authorized approver",
        )

    # Publish WebSocket event so the blocked session knows it was rejected
    _publish_approval_event(approval, "rejected")

    return _request_to_response(approval)


def _publish_approval_event(approval, resolution: str) -> None:
    """Publish WebSocket event for approval resolution (best-effort)."""
    try:
        from control_plane_api.app.lib.redis_client import get_redis_client
        import json

        redis = get_redis_client()
        if not redis:
            return

        event = {
            "event_type": "approval_resolved",
            "approval_id": str(approval.id),
            "execution_id": approval.execution_id,
            "status": resolution,
            "tool_name": approval.tool_name,
            "approved_by_email": approval.approved_by_email,
            "rejection_reason": approval.rejection_reason,
        }

        # Publish to the execution's WebSocket channel
        import asyncio
        channel = f"execution:{approval.execution_id}:events"

        async def _publish():
            await redis.publish(channel, json.dumps(event))

        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                loop.create_task(_publish())
            else:
                loop.run_until_complete(_publish())
        except RuntimeError:
            logger.warning("approval_event_publish_no_loop", approval_id=str(approval.id))

    except Exception as e:
        logger.warning("approval_event_publish_failed", error=str(e))
