"""
Approval service for managing approval request lifecycle.

Handles creation, status transitions, and querying of approval requests.
Publishes WebSocket events on approval resolution.
"""

import structlog
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from sqlalchemy import desc, or_

from control_plane_api.app.models.approval_request import ApprovalRequest

logger = structlog.get_logger()


class ApprovalService:
    """Service for managing approval request lifecycle."""

    def __init__(self, organization_id: str, db: Session):
        self.organization_id = organization_id
        self.db = db

    def create_request(
        self,
        execution_id: str,
        tool_name: str,
        requester_email: Optional[str] = None,
        requester_id: Optional[str] = None,
        rule_id: Optional[str] = None,
        tool_args: Optional[Dict[str, Any]] = None,
        risk_level: Optional[str] = None,
        enforcement_context: Optional[Dict[str, Any]] = None,
        approver_emails: Optional[List[str]] = None,
        timeout_minutes: int = 1440,
    ) -> ApprovalRequest:
        """Create a new approval request."""
        expires_at = datetime.utcnow() + timedelta(minutes=timeout_minutes)

        request = ApprovalRequest(
            organization_id=self.organization_id,
            execution_id=execution_id,
            rule_id=rule_id,
            status="pending",
            requester_email=requester_email,
            requester_id=requester_id,
            tool_name=tool_name,
            tool_args=tool_args,
            risk_level=risk_level,
            enforcement_context=enforcement_context,
            approver_emails=approver_emails or [],
            expires_at=expires_at,
        )
        self.db.add(request)
        self.db.commit()
        self.db.refresh(request)

        logger.info(
            "approval_request_created",
            approval_id=str(request.id),
            execution_id=execution_id,
            tool_name=tool_name,
            approver_emails=approver_emails,
        )
        return request

    def get_request(self, request_id: str) -> Optional[ApprovalRequest]:
        """Get a single approval request."""
        return self.db.query(ApprovalRequest).filter(
            ApprovalRequest.id == request_id,
            ApprovalRequest.organization_id == self.organization_id,
        ).first()

    def list_requests(
        self,
        status: Optional[str] = None,
        requester_email: Optional[str] = None,
        execution_id: Optional[str] = None,
        limit: int = 50,
        offset: int = 0,
    ) -> List[ApprovalRequest]:
        """List approval requests with filters."""
        query = self.db.query(ApprovalRequest).filter(
            ApprovalRequest.organization_id == self.organization_id
        )

        if status:
            query = query.filter(ApprovalRequest.status == status)
        if requester_email:
            query = query.filter(ApprovalRequest.requester_email == requester_email)
        if execution_id:
            query = query.filter(ApprovalRequest.execution_id == execution_id)

        return query.order_by(desc(ApprovalRequest.created_at)).offset(offset).limit(limit).all()

    def list_pending_for_approver(
        self,
        approver_email: str,
        user_group_names: Optional[List[str]] = None,
    ) -> List[ApprovalRequest]:
        """List pending requests where the current user is an authorized approver.

        A user is considered an authorized approver if:
        1. Their email is in the request's approver_emails list, OR
        2. Their Kubiya group names overlap with group-based approver rules
           (the approval_request.enforcement_context may contain the approver group name)
        """
        pending = self.db.query(ApprovalRequest).filter(
            ApprovalRequest.organization_id == self.organization_id,
            ApprovalRequest.status == "pending",
        ).order_by(desc(ApprovalRequest.created_at)).all()

        user_group_names = user_group_names or []

        results = []
        for req in pending:
            # Direct email match
            if req.approver_emails and approver_email in req.approver_emails:
                results.append(req)
                continue

            # Group-based match: check if approval rule's approver group overlaps
            # with the user's groups (stored in enforcement_context by the frontend)
            ctx = req.enforcement_context or {}
            approver_group = ctx.get("approver_group_name")
            if approver_group and approver_group in user_group_names:
                results.append(req)

        return results

    def approve_request(
        self,
        request_id: str,
        approver_email: str,
        approver_name: Optional[str] = None,
        comment: Optional[str] = None,
        user_group_names: Optional[List[str]] = None,
    ) -> Optional[ApprovalRequest]:
        """Approve a request. Returns None if not found or not authorized."""
        request = self.get_request(request_id)
        if not request:
            return None

        if request.status != "pending":
            logger.warning(
                "approval_request_not_pending",
                approval_id=str(request.id),
                current_status=request.status,
            )
            return None

        # Verify approver is authorized (email match OR group match)
        is_authorized = False
        if request.approver_emails and approver_email in request.approver_emails:
            is_authorized = True
        elif user_group_names:
            ctx = request.enforcement_context or {}
            approver_group = ctx.get("approver_group_name")
            if approver_group and approver_group in user_group_names:
                is_authorized = True

        if not is_authorized:
            logger.warning(
                "unauthorized_approver",
                approval_id=str(request.id),
                approver_email=approver_email,
            )
            return None

        request.status = "approved"
        request.approved_by_email = approver_email
        request.approved_by_name = approver_name
        request.resolved_at = datetime.utcnow()

        self.db.commit()
        self.db.refresh(request)

        logger.info(
            "approval_request_approved",
            approval_id=str(request.id),
            approved_by=approver_email,
        )
        return request

    def reject_request(
        self,
        request_id: str,
        rejector_email: str,
        rejector_name: Optional[str] = None,
        reason: Optional[str] = None,
        user_group_names: Optional[List[str]] = None,
    ) -> Optional[ApprovalRequest]:
        """Reject a request. Returns None if not found or not authorized."""
        request = self.get_request(request_id)
        if not request:
            return None

        if request.status != "pending":
            return None

        # Verify rejector is authorized (email match OR group match)
        is_authorized = False
        if request.approver_emails and rejector_email in request.approver_emails:
            is_authorized = True
        elif user_group_names:
            ctx = request.enforcement_context or {}
            approver_group = ctx.get("approver_group_name")
            if approver_group and approver_group in user_group_names:
                is_authorized = True

        if not is_authorized:
            logger.warning(
                "unauthorized_rejector",
                approval_id=str(request.id),
                rejector_email=rejector_email,
            )
            return None

        request.status = "rejected"
        request.approved_by_email = rejector_email
        request.approved_by_name = rejector_name
        request.rejection_reason = reason
        request.resolved_at = datetime.utcnow()

        self.db.commit()
        self.db.refresh(request)

        logger.info(
            "approval_request_rejected",
            approval_id=str(request.id),
            rejected_by=rejector_email,
            reason=reason,
        )
        return request

    def expire_stale_requests(self) -> int:
        """Expire requests past their timeout. Returns count expired."""
        now = datetime.utcnow()
        stale = self.db.query(ApprovalRequest).filter(
            ApprovalRequest.organization_id == self.organization_id,
            ApprovalRequest.status == "pending",
            ApprovalRequest.expires_at < now,
        ).all()

        for req in stale:
            req.status = "expired"
            req.resolved_at = now

        if stale:
            self.db.commit()
            logger.info("approval_requests_expired", count=len(stale))

        return len(stale)
