"""
Service for payment/invoice reconciliation and exception management.
"""
from sqlalchemy.orm import Session
from sqlalchemy import func, and_, or_
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
from ..models.payment import Payment, PaymentStatus
from ..models.invoice import Invoice, InvoiceStatus
from ..models.reconciliation_exception import ReconciliationException, ExceptionType, ExceptionStatus
from ...shared.config.logging_config import get_logger

logger = get_logger(__name__)


class ReconciliationService:
    """Service for reconciliation operations."""
    
    @staticmethod
    def run_reconciliation(
        db: Session,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None
    ) -> Dict[str, Any]:
        """Run reconciliation and detect exceptions."""
        if not start_date:
            start_date = datetime.utcnow() - timedelta(days=30)
        if not end_date:
            end_date = datetime.utcnow()
        
        exceptions = []
        
        # Get all completed payments in period
        payments = db.query(Payment).filter(
            and_(
                Payment.payment_status == PaymentStatus.completed,
                Payment.payment_date >= start_date,
                Payment.payment_date <= end_date
            )
        ).all()
        
        # Check for payments without invoices
        for payment in payments:
            invoice = db.query(Invoice).filter(
                Invoice.booking_id == payment.booking_id
            ).first()
            
            if not invoice:
                # Check if exception already exists
                existing = db.query(ReconciliationException).filter(
                    and_(
                        ReconciliationException.payment_id == payment.id,
                        ReconciliationException.exception_type == ExceptionType.missing_invoice,
                        ReconciliationException.status != ExceptionStatus.resolved,
                        ReconciliationException.status != ExceptionStatus.closed
                    )
                ).first()
                
                if not existing:
                    exception = ReconciliationException(
                        exception_type=ExceptionType.missing_invoice,
                        status=ExceptionStatus.open,
                        severity='high',
                        payment_id=payment.id,
                        booking_id=payment.booking_id,
                        description=f'Payment {payment.id} has no associated invoice',
                        actual_amount=float(payment.amount) if payment.amount else None,
                        exception_metadata={
                            'payment_date': payment.payment_date.isoformat() if payment.payment_date else None,
                            'payment_method': payment.payment_method.value if hasattr(payment.payment_method, 'value') else str(payment.payment_method)
                        }
                    )
                    db.add(exception)
                    exceptions.append(exception)
            else:
                # Check for amount mismatches
                payment_amount = float(payment.amount) if payment.amount else 0.0
                invoice_total = float(invoice.total_amount) if invoice.total_amount else 0.0
                invoice_paid = float(invoice.amount_paid) if invoice.amount_paid else 0.0
                
                # Check if payment amount matches invoice expectations
                if abs(payment_amount - (invoice_total - invoice_paid + payment_amount)) > 0.01:
                    # Potential mismatch - check if exception exists
                    existing = db.query(ReconciliationException).filter(
                        and_(
                            ReconciliationException.payment_id == payment.id,
                            ReconciliationException.invoice_id == invoice.id,
                            ReconciliationException.exception_type == ExceptionType.amount_mismatch,
                            ReconciliationException.status != ExceptionStatus.resolved,
                            ReconciliationException.status != ExceptionStatus.closed
                        )
                    ).first()
                    
                    if not existing:
                        difference = payment_amount - (invoice_total - invoice_paid)
                        exception = ReconciliationException(
                            exception_type=ExceptionType.amount_mismatch,
                            status=ExceptionStatus.open,
                            severity='high' if abs(difference) > 100 else 'medium',
                            payment_id=payment.id,
                            invoice_id=invoice.id,
                            booking_id=payment.booking_id,
                            description=f'Payment amount mismatch: Payment {payment.id} amount {payment_amount} vs Invoice {invoice.id}',
                            expected_amount=invoice_total - invoice_paid,
                            actual_amount=payment_amount,
                            difference=difference,
                            exception_metadata={
                                'invoice_total': invoice_total,
                                'invoice_paid': invoice_paid,
                                'payment_date': payment.payment_date.isoformat() if payment.payment_date else None
                            }
                        )
                        db.add(exception)
                        exceptions.append(exception)
        
        # Check for invoices without payments
        invoices = db.query(Invoice).filter(
            and_(
                Invoice.status == InvoiceStatus.paid,
                Invoice.paid_date >= start_date,
                Invoice.paid_date <= end_date
            )
        ).all()
        
        for invoice in invoices:
            payments_for_invoice = db.query(Payment).filter(
                Payment.booking_id == invoice.booking_id,
                Payment.payment_status == PaymentStatus.completed
            ).all()
            
            if not payments_for_invoice:
                # Check if exception exists
                existing = db.query(ReconciliationException).filter(
                    and_(
                        ReconciliationException.invoice_id == invoice.id,
                        ReconciliationException.exception_type == ExceptionType.missing_payment,
                        ReconciliationException.status != ExceptionStatus.resolved,
                        ReconciliationException.status != ExceptionStatus.closed
                    )
                ).first()
                
                if not existing:
                    exception = ReconciliationException(
                        exception_type=ExceptionType.missing_payment,
                        status=ExceptionStatus.open,
                        severity='critical',
                        invoice_id=invoice.id,
                        booking_id=invoice.booking_id,
                        description=f'Invoice {invoice.invoice_number} marked as paid but no payments found',
                        expected_amount=float(invoice.total_amount) if invoice.total_amount else None,
                        exception_metadata={
                            'invoice_date': invoice.paid_date.isoformat() if invoice.paid_date else None,
                            'invoice_status': invoice.status.value if hasattr(invoice.status, 'value') else str(invoice.status)
                        }
                    )
                    db.add(exception)
                    exceptions.append(exception)
        
        db.flush()
        
        return {
            'exceptions_created': len(exceptions),
            'exceptions': [{
                'id': exc.id,
                'type': exc.exception_type.value,
                'status': exc.status.value,
                'severity': exc.severity,
                'description': exc.description
            } for exc in exceptions]
        }
    
    @staticmethod
    def get_exceptions(
        db: Session,
        status: Optional[ExceptionStatus] = None,
        exception_type: Optional[ExceptionType] = None,
        assigned_to: Optional[int] = None,
        severity: Optional[str] = None,
        page: int = 1,
        limit: int = 50
    ) -> Dict[str, Any]:
        """Get reconciliation exceptions with filters."""
        query = db.query(ReconciliationException)
        
        if status:
            query = query.filter(ReconciliationException.status == status)
        if exception_type:
            query = query.filter(ReconciliationException.exception_type == exception_type)
        if assigned_to:
            query = query.filter(ReconciliationException.assigned_to == assigned_to)
        if severity:
            query = query.filter(ReconciliationException.severity == severity)
        
        total = query.count()
        offset = (page - 1) * limit
        exceptions = query.order_by(
            ReconciliationException.created_at.desc()
        ).offset(offset).limit(limit).all()
        
        exception_list = []
        for exc in exceptions:
            exception_list.append({
                'id': exc.id,
                'exception_type': exc.exception_type.value,
                'status': exc.status.value,
                'severity': exc.severity,
                'payment_id': exc.payment_id,
                'invoice_id': exc.invoice_id,
                'booking_id': exc.booking_id,
                'description': exc.description,
                'expected_amount': float(exc.expected_amount) if exc.expected_amount else None,
                'actual_amount': float(exc.actual_amount) if exc.actual_amount else None,
                'difference': float(exc.difference) if exc.difference else None,
                'assigned_to': exc.assigned_to,
                'assigned_at': exc.assigned_at.isoformat() if exc.assigned_at else None,
                'resolved_by': exc.resolved_by,
                'resolved_at': exc.resolved_at.isoformat() if exc.resolved_at else None,
                'resolution_notes': exc.resolution_notes,
                'comments': exc.comments or [],
                'created_at': exc.created_at.isoformat() if exc.created_at else None,
                'updated_at': exc.updated_at.isoformat() if exc.updated_at else None
            })
        
        return {
            'exceptions': exception_list,
            'pagination': {
                'total': total,
                'page': page,
                'limit': limit,
                'total_pages': (total + limit - 1) // limit
            }
        }
    
    @staticmethod
    def assign_exception(
        db: Session,
        exception_id: int,
        assigned_to: int
    ) -> ReconciliationException:
        """Assign an exception to a user."""
        exception = db.query(ReconciliationException).filter(
            ReconciliationException.id == exception_id
        ).first()
        
        if not exception:
            raise ValueError("Exception not found")
        
        exception.assigned_to = assigned_to
        exception.assigned_at = datetime.utcnow()
        exception.status = ExceptionStatus.assigned
        
        db.flush()
        return exception
    
    @staticmethod
    def resolve_exception(
        db: Session,
        exception_id: int,
        resolved_by: int,
        resolution_notes: str
    ) -> ReconciliationException:
        """Resolve an exception."""
        exception = db.query(ReconciliationException).filter(
            ReconciliationException.id == exception_id
        ).first()
        
        if not exception:
            raise ValueError("Exception not found")
        
        exception.status = ExceptionStatus.resolved
        exception.resolved_by = resolved_by
        exception.resolved_at = datetime.utcnow()
        exception.resolution_notes = resolution_notes
        
        db.flush()
        return exception
    
    @staticmethod
    def add_comment(
        db: Session,
        exception_id: int,
        user_id: int,
        comment: str
    ) -> ReconciliationException:
        """Add a comment to an exception."""
        exception = db.query(ReconciliationException).filter(
            ReconciliationException.id == exception_id
        ).first()
        
        if not exception:
            raise ValueError("Exception not found")
        
        comments = exception.comments or []
        comments.append({
            'user_id': user_id,
            'comment': comment,
            'created_at': datetime.utcnow().isoformat()
        })
        exception.comments = comments
        
        db.flush()
        return exception


# Singleton instance
reconciliation_service = ReconciliationService()

