"""
Service for handling financial approval workflows with segregation of duties.
"""
from sqlalchemy.orm import Session
from typing import Optional, Dict, Any, List
from datetime import datetime
from ..models.financial_approval import FinancialApproval, ApprovalStatus, ApprovalActionType
from ...auth.models.user import User
from ...shared.utils.role_helpers import is_accountant_approver, is_accountant_readonly, is_admin
from ...shared.config.logging_config import get_logger

logger = get_logger(__name__)


class ApprovalService:
    """Service for managing financial approvals."""
    
    # Configurable thresholds (can be moved to system settings)
    LARGE_REFUND_THRESHOLD = 1000.0  # $1000
    LARGE_DISCOUNT_THRESHOLD = 500.0  # $500 or 20% of invoice
    LARGE_DISCOUNT_PERCENTAGE_THRESHOLD = 20.0  # 20%
    
    @staticmethod
    def requires_approval(
        action_type: ApprovalActionType,
        amount: Optional[float] = None,
        invoice_total: Optional[float] = None,
        db: Session = None
    ) -> bool:
        """Check if an action requires approval based on thresholds."""
        # Admin actions don't require approval
        # (This check should be done at the route level, but we include it here for safety)
        
        if action_type == ApprovalActionType.large_refund:
            return amount is not None and amount >= ApprovalService.LARGE_REFUND_THRESHOLD
        
        if action_type == ApprovalActionType.large_discount:
            if amount is not None and amount >= ApprovalService.LARGE_DISCOUNT_THRESHOLD:
                return True
            if invoice_total and amount:
                discount_percentage = (amount / invoice_total) * 100
                return discount_percentage >= ApprovalService.LARGE_DISCOUNT_PERCENTAGE_THRESHOLD
            return False
        
        if action_type == ApprovalActionType.invoice_write_off:
            return True  # All write-offs require approval
        
        if action_type == ApprovalActionType.payment_status_override:
            return True  # All manual status overrides require approval
        
        if action_type == ApprovalActionType.tax_rate_change:
            return True  # All tax rate changes require approval
        
        if action_type == ApprovalActionType.manual_payment_adjustment:
            return amount is not None and amount >= ApprovalService.LARGE_REFUND_THRESHOLD
        
        return False
    
    @staticmethod
    def can_approve(user: User, approval: FinancialApproval, db: Session) -> tuple[bool, str]:
        """
        Check if a user can approve a specific approval request.
        Returns (can_approve: bool, reason: str)
        Implements segregation of duties - users cannot approve their own actions.
        """
        # Admin can always approve
        if is_admin(user, db):
            return True, "Admin approval"
        
        # Only approvers can approve
        if not is_accountant_approver(user, db):
            return False, "User does not have approval permissions"
        
        # Segregation of duties: users cannot approve their own requests
        if approval.requested_by == user.id:
            return False, "Users cannot approve their own requests (segregation of duties)"
        
        # Check if already approved/rejected
        if approval.status != ApprovalStatus.pending:
            return False, f"Approval request is already {approval.status.value}"
        
        return True, "Approval allowed"
    
    @staticmethod
    def create_approval_request(
        db: Session,
        action_type: ApprovalActionType,
        requested_by: int,
        action_description: str,
        amount: Optional[float] = None,
        payment_id: Optional[int] = None,
        invoice_id: Optional[int] = None,
        booking_id: Optional[int] = None,
        previous_value: Optional[Dict[str, Any]] = None,
        new_value: Optional[Dict[str, Any]] = None,
        request_reason: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> FinancialApproval:
        """Create a new approval request."""
        user = db.query(User).filter(User.id == requested_by).first()
        if not user:
            raise ValueError("Requesting user not found")
        
        approval = FinancialApproval(
            action_type=action_type,
            action_description=action_description,
            status=ApprovalStatus.pending,
            requested_by=requested_by,
            requested_by_email=user.email,
            amount=amount,
            payment_id=payment_id,
            invoice_id=invoice_id,
            booking_id=booking_id,
            previous_value=previous_value,
            new_value=new_value,
            request_reason=request_reason,
            approval_metadata=metadata or {}
        )
        
        db.add(approval)
        db.flush()
        return approval
    
    @staticmethod
    def approve_request(
        db: Session,
        approval_id: int,
        approved_by: int,
        approval_notes: Optional[str] = None
    ) -> FinancialApproval:
        """Approve an approval request."""
        approval = db.query(FinancialApproval).filter(FinancialApproval.id == approval_id).first()
        if not approval:
            raise ValueError("Approval request not found")
        
        approver = db.query(User).filter(User.id == approved_by).first()
        if not approver:
            raise ValueError("Approver user not found")
        
        can_approve, reason = ApprovalService.can_approve(approver, approval, db)
        if not can_approve:
            raise ValueError(f"Cannot approve: {reason}")
        
        approval.status = ApprovalStatus.approved
        approval.approved_by = approved_by
        approval.approved_by_email = approver.email
        approval.approval_notes = approval_notes
        approval.approved_at = datetime.utcnow()
        
        db.flush()
        return approval
    
    @staticmethod
    def reject_request(
        db: Session,
        approval_id: int,
        rejected_by: int,
        rejection_reason: str
    ) -> FinancialApproval:
        """Reject an approval request."""
        approval = db.query(FinancialApproval).filter(FinancialApproval.id == approval_id).first()
        if not approval:
            raise ValueError("Approval request not found")
        
        rejector = db.query(User).filter(User.id == rejected_by).first()
        if not rejector:
            raise ValueError("Rejector user not found")
        
        # Check permissions (same as approval)
        can_approve, reason = ApprovalService.can_approve(rejector, approval, db)
        if not can_approve:
            raise ValueError(f"Cannot reject: {reason}")
        
        if approval.status != ApprovalStatus.pending:
            raise ValueError(f"Approval request is already {approval.status.value}")
        
        approval.status = ApprovalStatus.rejected
        approval.rejected_by = rejected_by
        approval.rejection_reason = rejection_reason
        approval.rejected_at = datetime.utcnow()
        
        db.flush()
        return approval
    
    @staticmethod
    def get_pending_approvals(
        db: Session,
        action_type: Optional[ApprovalActionType] = None,
        limit: int = 50
    ) -> List[FinancialApproval]:
        """Get pending approval requests."""
        query = db.query(FinancialApproval).filter(
            FinancialApproval.status == ApprovalStatus.pending
        )
        
        if action_type:
            query = query.filter(FinancialApproval.action_type == action_type)
        
        return query.order_by(FinancialApproval.created_at.desc()).limit(limit).all()
    
    @staticmethod
    def get_approval_by_id(db: Session, approval_id: int) -> Optional[FinancialApproval]:
        """Get an approval request by ID."""
        return db.query(FinancialApproval).filter(FinancialApproval.id == approval_id).first()
    
    @staticmethod
    def get_approvals_for_action(
        db: Session,
        action_type: ApprovalActionType,
        payment_id: Optional[int] = None,
        invoice_id: Optional[int] = None,
        booking_id: Optional[int] = None
    ) -> List[FinancialApproval]:
        """Get approval requests for a specific action/entity."""
        query = db.query(FinancialApproval).filter(
            FinancialApproval.action_type == action_type
        )
        
        if payment_id:
            query = query.filter(FinancialApproval.payment_id == payment_id)
        if invoice_id:
            query = query.filter(FinancialApproval.invoice_id == invoice_id)
        if booking_id:
            query = query.filter(FinancialApproval.booking_id == booking_id)
        
        return query.order_by(FinancialApproval.created_at.desc()).all()
    
    @staticmethod
    def get_approvals(
        db: Session,
        status: Optional[ApprovalStatus] = None,
        action_type: Optional[ApprovalActionType] = None,
        requested_by: Optional[int] = None,
        approved_by: Optional[int] = None,
        page: int = 1,
        limit: int = 50
    ) -> List[FinancialApproval]:
        """Get approval requests with optional filters."""
        query = db.query(FinancialApproval)
        
        if status:
            query = query.filter(FinancialApproval.status == status)
        if action_type:
            query = query.filter(FinancialApproval.action_type == action_type)
        if requested_by:
            query = query.filter(FinancialApproval.requested_by == requested_by)
        if approved_by:
            query = query.filter(FinancialApproval.approved_by == approved_by)
        
        # Pagination
        offset = (page - 1) * limit
        return query.order_by(FinancialApproval.created_at.desc()).offset(offset).limit(limit).all()


# Singleton instance
approval_service = ApprovalService()

