from fastapi import APIRouter, Depends, HTTPException, Query, Response, Request
from sqlalchemy.orm import Session
from sqlalchemy import func, and_, or_
from typing import Optional
from datetime import datetime, timedelta
import csv
import io
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import authorize_roles
from ...security.middleware.step_up_auth import authorize_financial_access
from ...auth.models.user import User
from ..models.payment import Payment, PaymentStatus, PaymentMethod
from ..models.invoice import Invoice, InvoiceStatus
from ...bookings.models.booking import Booking, BookingStatus
from ...shared.utils.response_helpers import success_response
from ..services.financial_audit_service import financial_audit_service
from ..models.financial_audit_trail import FinancialActionType
from ..services.gl_service import gl_service
from ..models.chart_of_accounts import ChartOfAccounts, AccountType
from ..models.journal_entry import JournalEntryStatus
from sqlalchemy import func, and_, or_

logger = get_logger(__name__)
router = APIRouter(prefix='/financial', tags=['financial'])

@router.get('/profit-loss')
async def get_profit_loss_report(
    start_date: Optional[str] = Query(None),
    end_date: Optional[str] = Query(None),
    current_user: User = Depends(authorize_financial_access('admin', 'accountant')),
    db: Session = Depends(get_db)
):
    """Generate Profit & Loss statement."""
    try:
        # Parse dates
        if start_date:
            start = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
        else:
            start = datetime.utcnow().replace(day=1, hour=0, minute=0, second=0)
        
        if end_date:
            end = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
        else:
            end = datetime.utcnow()
        
        # Revenue (completed payments)
        revenue_query = db.query(func.sum(Payment.amount)).filter(
            and_(
                Payment.payment_status == PaymentStatus.completed,
                Payment.payment_date >= start,
                Payment.payment_date <= end
            )
        )
        total_revenue = revenue_query.scalar() or 0.0
        
        # Revenue by source
        revenue_by_method = db.query(
            Payment.payment_method,
            func.sum(Payment.amount).label('total')
        ).filter(
            and_(
                Payment.payment_status == PaymentStatus.completed,
                Payment.payment_date >= start,
                Payment.payment_date <= end
            )
        ).group_by(Payment.payment_method).all()
        
        revenue_breakdown = {}
        for method, total in revenue_by_method:
            method_name = method.value if hasattr(method, 'value') else str(method)
            revenue_breakdown[method_name] = float(total or 0)
        
        # Tax collected (from invoices)
        tax_collected = db.query(func.sum(Invoice.tax_amount)).filter(
            and_(
                Invoice.status == InvoiceStatus.paid,
                Invoice.paid_date >= start,
                Invoice.paid_date <= end
            )
        ).scalar() or 0.0
        
        # Discounts given
        discounts = db.query(func.sum(Invoice.discount_amount)).filter(
            and_(
                Invoice.status == InvoiceStatus.paid,
                Invoice.paid_date >= start,
                Invoice.paid_date <= end
            )
        ).scalar() or 0.0
        
        # Try to get data from GL if available
        try:
            from ..models.journal_entry import JournalLine
            from ..models.fiscal_period import FiscalPeriod
            
            # Get periods for date range
            periods = db.query(FiscalPeriod).filter(
                and_(
                    FiscalPeriod.start_date <= end,
                    FiscalPeriod.end_date >= start
                )
            ).all()
            
            period_ids = [p.id for p in periods] if periods else []
            
            # Get revenue from GL (credit side of revenue accounts)
            if period_ids:
                gl_revenue = db.query(func.sum(JournalLine.credit_amount)).join(
                    JournalEntry
                ).filter(
                    and_(
                        JournalEntry.fiscal_period_id.in_(period_ids),
                        JournalEntry.status == JournalEntryStatus.posted,
                        JournalEntry.entry_date >= start,
                        JournalEntry.entry_date <= end
                    )
                ).join(
                    ChartOfAccounts
                ).filter(
                    ChartOfAccounts.account_type == AccountType.revenue
                ).scalar() or 0.0
                
                # Use GL revenue if available, otherwise fall back to payment data
                if gl_revenue > 0:
                    net_revenue = float(gl_revenue) - discounts
                else:
                    net_revenue = total_revenue - discounts
            else:
                net_revenue = total_revenue - discounts
            
            # Get COGS from GL
            cogs = 0.0
            if period_ids:
                gl_cogs = db.query(func.sum(JournalLine.debit_amount)).join(
                    JournalEntry
                ).filter(
                    and_(
                        JournalEntry.fiscal_period_id.in_(period_ids),
                        JournalEntry.status == JournalEntryStatus.posted,
                        JournalEntry.entry_date >= start,
                        JournalEntry.entry_date <= end
                    )
                ).join(
                    ChartOfAccounts
                ).filter(
                    ChartOfAccounts.account_type == AccountType.cogs
                ).scalar() or 0.0
                cogs = float(gl_cogs)
            
            # Get operating expenses from GL
            operating_expenses_gl = 0.0
            if period_ids:
                gl_expenses = db.query(func.sum(JournalLine.debit_amount)).join(
                    JournalEntry
                ).filter(
                    and_(
                        JournalEntry.fiscal_period_id.in_(period_ids),
                        JournalEntry.status == JournalEntryStatus.posted,
                        JournalEntry.entry_date >= start,
                        JournalEntry.entry_date <= end
                    )
                ).join(
                    ChartOfAccounts
                ).filter(
                    ChartOfAccounts.account_type == AccountType.expense
                ).scalar() or 0.0
                operating_expenses_gl = float(gl_expenses)
            
            # Fallback to refunds if no GL data
            refunds = db.query(func.sum(Payment.amount)).filter(
                and_(
                    Payment.payment_status == PaymentStatus.refunded,
                    Payment.payment_date >= start,
                    Payment.payment_date <= end
                )
            ).scalar() or 0.0
            
            # Use GL COGS if available, otherwise estimate
            if cogs > 0:
                estimated_cogs = cogs
            else:
                # Fallback: estimate 30% if no GL data
                estimated_cogs = net_revenue * 0.30
            
            # Use GL expenses if available, otherwise use refunds as proxy
            if operating_expenses_gl > 0:
                operating_expenses = operating_expenses_gl
            else:
                operating_expenses = float(refunds)
            
            gross_profit = net_revenue - estimated_cogs
        except Exception as e:
            # Fallback to original logic if GL not available
            logger.warning(f'Error getting GL data for P&L: {str(e)}, using fallback calculations')
            refunds = db.query(func.sum(Payment.amount)).filter(
                and_(
                    Payment.payment_status == PaymentStatus.refunded,
                    Payment.payment_date >= start,
                    Payment.payment_date <= end
                )
            ).scalar() or 0.0
            
            net_revenue = total_revenue - discounts
            estimated_cogs = net_revenue * 0.30
            gross_profit = net_revenue - estimated_cogs
            operating_expenses = float(refunds)
        
        # Net profit
        net_profit = gross_profit - operating_expenses
        
        return success_response(data={
            'period': {
                'start_date': start.isoformat(),
                'end_date': end.isoformat()
            },
            'revenue': {
                'total_revenue': float(total_revenue),
                'revenue_by_method': revenue_breakdown,
                'tax_collected': float(tax_collected),
                'discounts': float(discounts),
                'net_revenue': float(net_revenue)
            },
            'costs': {
                'estimated_cogs': float(estimated_cogs),
                'gross_profit': float(gross_profit)
            },
            'expenses': {
                'refunds': float(refunds),
                'operating_expenses': float(operating_expenses)
            },
            'profit': {
                'net_profit': float(net_profit),
                'profit_margin': float((net_profit / net_revenue * 100) if net_revenue > 0 else 0)
            }
        })
    except Exception as e:
        logger.error(f'Error generating P&L report: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/balance-sheet')
async def get_balance_sheet(
    as_of_date: Optional[str] = Query(None),
    current_user: User = Depends(authorize_financial_access('admin', 'accountant')),
    db: Session = Depends(get_db)
):
    """Generate Balance Sheet statement."""
    try:
        if as_of_date:
            as_of = datetime.fromisoformat(as_of_date.replace('Z', '+00:00'))
        else:
            as_of = datetime.utcnow()
        
        # Assets
        # Cash (completed payments - refunds)
        total_cash = db.query(func.sum(Payment.amount)).filter(
            and_(
                Payment.payment_status == PaymentStatus.completed,
                Payment.payment_date <= as_of
            )
        ).scalar() or 0.0
        
        refunds_total = db.query(func.sum(Payment.amount)).filter(
            and_(
                Payment.payment_status == PaymentStatus.refunded,
                Payment.payment_date <= as_of
            )
        ).scalar() or 0.0
        
        cash = total_cash - refunds_total
        
        # Accounts Receivable (unpaid invoices)
        accounts_receivable = db.query(func.sum(Invoice.balance_due)).filter(
            and_(
                Invoice.status != InvoiceStatus.paid,
                Invoice.issue_date <= as_of
            )
        ).scalar() or 0.0
        
        total_assets = cash + accounts_receivable
        
        # Liabilities
        # Try to get Accounts Payable from GL
        accounts_payable = 0.0
        try:
            from ..models.journal_entry import JournalLine
            from ..models.chart_of_accounts import ChartOfAccounts
            
            # Get AP balance from GL (credit balance of AP account)
            ap_account = db.query(ChartOfAccounts).filter(
                ChartOfAccounts.account_code == gl_service.ACCOUNT_CODES.get('accounts_payable', '2000')
            ).first()
            
            if ap_account:
                ap_balance = db.query(
                    func.sum(JournalLine.credit_amount) - func.sum(JournalLine.debit_amount)
                ).join(
                    JournalEntry
                ).filter(
                    and_(
                        JournalEntry.status == JournalEntryStatus.posted,
                        JournalEntry.entry_date <= as_of
                    )
                ).filter(
                    JournalLine.account_id == ap_account.id
                ).scalar() or 0.0
                accounts_payable = float(ap_balance) if ap_balance > 0 else 0.0
        except Exception as e:
            logger.warning(f'Error getting AP from GL: {str(e)}, using placeholder')
            accounts_payable = 0.0
        
        # Deferred Revenue (deposits for future bookings)
        deferred_revenue = db.query(func.sum(Payment.amount)).filter(
            and_(
                Payment.payment_type == 'deposit',
                Payment.payment_status == PaymentStatus.completed,
                Payment.payment_date <= as_of
            )
        ).scalar() or 0.0
        
        total_liabilities = accounts_payable + deferred_revenue
        
        # Equity
        # Retained Earnings - try to get from GL, otherwise calculate
        retained_earnings = 0.0
        try:
            from ..models.journal_entry import JournalLine
            from ..models.chart_of_accounts import ChartOfAccounts
            
            # Get retained earnings from GL
            re_account = db.query(ChartOfAccounts).filter(
                ChartOfAccounts.account_code == gl_service.ACCOUNT_CODES.get('retained_earnings', '3000')
            ).first()
            
            if re_account:
                re_balance = db.query(
                    func.sum(JournalLine.credit_amount) - func.sum(JournalLine.debit_amount)
                ).join(
                    JournalEntry
                ).filter(
                    and_(
                        JournalEntry.status == JournalEntryStatus.posted,
                        JournalEntry.entry_date <= as_of
                    )
                ).filter(
                    JournalLine.account_id == re_account.id
                ).scalar() or 0.0
                retained_earnings = float(re_balance)
            else:
                # Fallback calculation
                all_revenue = db.query(func.sum(Payment.amount)).filter(
                    and_(
                        Payment.payment_status == PaymentStatus.completed,
                        Payment.payment_date <= as_of
                    )
                ).scalar() or 0.0
                
                all_refunds = db.query(func.sum(Payment.amount)).filter(
                    and_(
                        Payment.payment_status == PaymentStatus.refunded,
                        Payment.payment_date <= as_of
                    )
                ).scalar() or 0.0
                
                retained_earnings = all_revenue - all_refunds - (all_revenue * 0.30)
        except Exception as e:
            logger.warning(f'Error getting retained earnings from GL: {str(e)}, using fallback')
            all_revenue = db.query(func.sum(Payment.amount)).filter(
                and_(
                    Payment.payment_status == PaymentStatus.completed,
                    Payment.payment_date <= as_of
                )
            ).scalar() or 0.0
            
            all_refunds = db.query(func.sum(Payment.amount)).filter(
                and_(
                    Payment.payment_status == PaymentStatus.refunded,
                    Payment.payment_date <= as_of
                )
            ).scalar() or 0.0
            
            retained_earnings = all_revenue - all_refunds - (all_revenue * 0.30)
        
        total_equity = retained_earnings
        
        return success_response(data={
            'as_of_date': as_of.isoformat(),
            'assets': {
                'cash': float(cash),
                'accounts_receivable': float(accounts_receivable),
                'total_assets': float(total_assets)
            },
            'liabilities': {
                'accounts_payable': float(accounts_payable),
                'deferred_revenue': float(deferred_revenue),
                'total_liabilities': float(total_liabilities)
            },
            'equity': {
                'retained_earnings': float(retained_earnings),
                'total_equity': float(total_equity)
            },
            'balance': {
                'total_liabilities_and_equity': float(total_liabilities + total_equity),
                'is_balanced': abs(total_assets - (total_liabilities + total_equity)) < 0.01
            }
        })
    except Exception as e:
        logger.error(f'Error generating balance sheet: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/tax-report')
async def get_tax_report(
    request: Request,
    start_date: Optional[str] = Query(None),
    end_date: Optional[str] = Query(None),
    format: Optional[str] = Query('json', regex='^(json|csv)$'),
    current_user: User = Depends(authorize_financial_access('admin', 'accountant')),
    db: Session = Depends(get_db)
):
    """Generate tax report with export capability. Requires step-up authentication for exports."""
    client_ip = request.client.host if request.client else None
    
    # Log activity for exports
    if format == 'csv':
        try:
            from ...payments.services.accountant_security_service import accountant_security_service
            user_agent = request.headers.get('User-Agent')
            
            accountant_security_service.log_activity(
                db=db,
                user_id=current_user.id,
                activity_type='data_export',
                activity_description='Tax report exported as CSV',
                ip_address=client_ip,
                user_agent=user_agent,
                risk_level='high',
                metadata={'export_type': 'tax_report', 'format': 'csv'}
            )
        except Exception as e:
            logger.warning(f'Error logging export activity: {e}')
    user_agent = request.headers.get('User-Agent')
    request_id = getattr(request.state, 'request_id', None)
    
    try:
        if start_date:
            start = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
        else:
            start = datetime.utcnow().replace(day=1, hour=0, minute=0, second=0)
        
        if end_date:
            end = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
        else:
            end = datetime.utcnow()
        
        # Get all paid invoices in period
        invoices = db.query(Invoice).filter(
            and_(
                Invoice.status == InvoiceStatus.paid,
                Invoice.paid_date >= start,
                Invoice.paid_date <= end
            )
        ).all()
        
        tax_data = []
        total_tax = 0.0
        total_revenue = 0.0
        
        for invoice in invoices:
            tax_amount = float(invoice.tax_amount) if invoice.tax_amount else 0.0
            revenue = float(invoice.total_amount) if invoice.total_amount else 0.0
            total_tax += tax_amount
            total_revenue += revenue
            
            tax_data.append({
                'invoice_number': invoice.invoice_number,
                'date': invoice.paid_date.isoformat() if invoice.paid_date else None,
                'customer_name': invoice.customer_name,
                'customer_tax_id': invoice.customer_tax_id,
                'subtotal': float(invoice.subtotal) if invoice.subtotal else 0.0,
                'tax_rate': float(invoice.tax_rate) if invoice.tax_rate else 0.0,
                'tax_amount': tax_amount,
                'total_amount': revenue
            })
        
        if format == 'csv':
            # Generate CSV
            output = io.StringIO()
            writer = csv.DictWriter(output, fieldnames=[
                'invoice_number', 'date', 'customer_name', 'customer_tax_id',
                'subtotal', 'tax_rate', 'tax_amount', 'total_amount'
            ])
            writer.writeheader()
            writer.writerows(tax_data)
            
            # Log financial audit for data export
            try:
                financial_audit_service.log_financial_action(
                    db=db,
                    action_type=FinancialActionType.data_exported,
                    performed_by=current_user.id,
                    action_description=f'Tax report exported as CSV for period {start.strftime("%Y-%m-%d")} to {end.strftime("%Y-%m-%d")}',
                    amount=total_tax,
                    metadata={
                        'export_type': 'tax_report',
                        'format': 'csv',
                        'start_date': start.isoformat(),
                        'end_date': end.isoformat(),
                        'invoice_count': len(tax_data),
                        'total_revenue': float(total_revenue),
                        'total_tax': float(total_tax),
                        'ip_address': client_ip,
                        'user_agent': user_agent,
                        'request_id': request_id
                    },
                    notes=f'Tax report CSV export by {current_user.email}'
                )
            except Exception as e:
                logger.warning(f'Failed to log financial audit for tax report export: {e}')
            
            return Response(
                content=output.getvalue(),
                media_type='text/csv',
                headers={
                    'Content-Disposition': f'attachment; filename="tax_report_{start.strftime("%Y%m%d")}_{end.strftime("%Y%m%d")}.csv"'
                }
            )
        
        return success_response(data={
            'period': {
                'start_date': start.isoformat(),
                'end_date': end.isoformat()
            },
            'summary': {
                'total_revenue': float(total_revenue),
                'total_tax_collected': float(total_tax),
                'invoice_count': len(tax_data)
            },
            'transactions': tax_data
        })
    except Exception as e:
        logger.error(f'Error generating tax report: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/payment-reconciliation')
async def get_payment_reconciliation(
    start_date: Optional[str] = Query(None),
    end_date: Optional[str] = Query(None),
    include_exceptions: bool = Query(True),
    current_user: User = Depends(authorize_financial_access('admin', 'accountant')),
    db: Session = Depends(get_db)
):
    """Generate payment reconciliation report with exception integration."""
    try:
        if start_date:
            start = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
        else:
            start = datetime.utcnow() - timedelta(days=30)
        
        if end_date:
            end = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
        else:
            end = datetime.utcnow()
        
        # Get all payments in period
        payments = db.query(Payment).filter(
            and_(
                Payment.created_at >= start,
                Payment.created_at <= end
            )
        ).all()
        
        reconciliation = {
            'period': {
                'start_date': start.isoformat(),
                'end_date': end.isoformat()
            },
            'by_status': {},
            'by_method': {},
            'discrepancies': []
        }
        
        # Group by status
        for status in PaymentStatus:
            status_payments = [p for p in payments if p.payment_status == status]
            total = sum(float(p.amount) for p in status_payments)
            reconciliation['by_status'][status.value] = {
                'count': len(status_payments),
                'total_amount': float(total)
            }
        
        # Group by payment method
        for method in PaymentMethod:
            method_payments = [p for p in payments if p.payment_method == method]
            total = sum(float(p.amount) for p in method_payments)
            reconciliation['by_method'][method.value] = {
                'count': len(method_payments),
                'total_amount': float(total)
            }
        
        # Get exceptions if requested
        if include_exceptions:
            try:
                from ..services.reconciliation_service import reconciliation_service
                from ..models.reconciliation_exception import ExceptionStatus
                
                exceptions_data = reconciliation_service.get_exceptions(
                    db=db,
                    status=ExceptionStatus.open,
                    page=1,
                    limit=100
                )
                
                reconciliation['exceptions'] = {
                    'open_count': exceptions_data['pagination']['total'],
                    'recent_exceptions': exceptions_data['exceptions'][:10]  # Top 10
                }
            except Exception as e:
                logger.warning(f'Error fetching exceptions: {str(e)}')
                reconciliation['exceptions'] = {'open_count': 0, 'recent_exceptions': []}
        
        # Find discrepancies (payments without matching invoices, etc.)
        for payment in payments:
            if payment.payment_status == PaymentStatus.completed:
                # Check if invoice exists
                invoice = db.query(Invoice).filter(
                    Invoice.booking_id == payment.booking_id
                ).first()
                
                if not invoice:
                    reconciliation['discrepancies'].append({
                        'type': 'missing_invoice',
                        'payment_id': payment.id,
                        'booking_id': payment.booking_id,
                        'amount': float(payment.amount),
                        'date': payment.payment_date.isoformat() if payment.payment_date else None
                    })
        
        return success_response(data=reconciliation)
    except Exception as e:
        logger.error(f'Error generating payment reconciliation: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/refunds')
async def get_refund_history(
    start_date: Optional[str] = Query(None),
    end_date: Optional[str] = Query(None),
    current_user: User = Depends(authorize_financial_access('admin', 'accountant')),
    db: Session = Depends(get_db)
):
    """Get refund history and statistics."""
    try:
        query = db.query(Payment).filter(
            Payment.payment_status == PaymentStatus.refunded
        )
        
        if start_date:
            start = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
            query = query.filter(Payment.payment_date >= start)
        
        if end_date:
            end = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
            query = query.filter(Payment.payment_date <= end)
        
        refunds = query.order_by(Payment.payment_date.desc()).all()
        
        total_refunded = sum(float(p.amount) for p in refunds)
        
        refund_list = []
        for refund in refunds:
            refund_list.append({
                'id': refund.id,
                'booking_id': refund.booking_id,
                'amount': float(refund.amount),
                'payment_method': refund.payment_method.value if hasattr(refund.payment_method, 'value') else str(refund.payment_method),
                'refund_date': refund.payment_date.isoformat() if refund.payment_date else None,
                'transaction_id': refund.transaction_id,
                'notes': refund.notes
            })
        
        return success_response(data={
            'total_refunds': len(refunds),
            'total_refunded_amount': float(total_refunded),
            'refunds': refund_list
        })
    except Exception as e:
        logger.error(f'Error fetching refund history: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

