"""
Payment Service
Wallet management, transactions, and withdrawal processing
"""
import secrets
import string
from datetime import datetime
from typing import Optional, List, Tuple
import logging

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, func

from app.models.payment import (
    Wallet, Transaction, Payment, WithdrawalRequest,
    TransactionType, TransactionStatus, PaymentMethod, WithdrawalStatus
)
from app.models.ride import Ride
from app.config import settings

logger = logging.getLogger(__name__)


class PaymentService:
    """Payment and wallet management service"""
    
    @staticmethod
    def generate_transaction_ref(length: int = 16) -> str:
        """Generate unique transaction reference"""
        chars = string.ascii_uppercase + string.digits
        return "TXN" + "".join(secrets.choice(chars) for _ in range(length))
    
    @classmethod
    async def get_wallet(cls, db: AsyncSession, user_id: int) -> Optional[Wallet]:
        """Get user's wallet"""
        query = select(Wallet).where(Wallet.user_id == user_id)
        result = await db.execute(query)
        return result.scalar_one_or_none()
    
    @classmethod
    async def create_wallet(cls, db: AsyncSession, user_id: int) -> Wallet:
        """Create wallet for user if not exists"""
        existing = await cls.get_wallet(db, user_id)
        if existing:
            return existing
        
        wallet = Wallet(user_id=user_id)
        db.add(wallet)
        await db.commit()
        await db.refresh(wallet)
        return wallet
    
    @classmethod
    async def topup_wallet(
        cls,
        db: AsyncSession,
        user_id: int,
        amount: int,
        payment_method: PaymentMethod = PaymentMethod.CARD,
        gateway_transaction_id: Optional[str] = None
    ) -> Transaction:
        """Add funds to wallet"""
        if amount <= 0:
            raise ValueError("Amount must be positive")
        
        wallet = await cls.get_wallet(db, user_id)
        if not wallet:
            wallet = await cls.create_wallet(db, user_id)
        
        balance_before = wallet.balance
        wallet.balance += amount
        wallet.lifetime_credits += amount
        
        # Create transaction record
        transaction = Transaction(
            transaction_ref=cls.generate_transaction_ref(),
            wallet_id=wallet.id,
            transaction_type=TransactionType.WALLET_TOPUP,
            amount=amount,
            fee=0,
            net_amount=amount,
            balance_before=balance_before,
            balance_after=wallet.balance,
            status=TransactionStatus.COMPLETED,
            payment_gateway=payment_method.value,
            gateway_transaction_id=gateway_transaction_id,
            description=f"Wallet top-up via {payment_method.value}"
        )
        
        db.add(transaction)
        await db.commit()
        await db.refresh(transaction)
        
        return transaction
    
    @classmethod
    async def deduct_from_wallet(
        cls,
        db: AsyncSession,
        user_id: int,
        amount: int,
        transaction_type: TransactionType,
        ride_id: Optional[int] = None,
        description: Optional[str] = None
    ) -> Transaction:
        """Deduct funds from wallet"""
        if amount <= 0:
            raise ValueError("Amount must be positive")
        
        wallet = await cls.get_wallet(db, user_id)
        if not wallet:
            raise ValueError("Wallet not found")
        
        if wallet.available_balance < amount:
            raise ValueError("Insufficient wallet balance")
        
        balance_before = wallet.balance
        wallet.balance -= amount
        wallet.lifetime_debits += amount
        
        # Create transaction record
        transaction = Transaction(
            transaction_ref=cls.generate_transaction_ref(),
            wallet_id=wallet.id,
            ride_id=ride_id,
            transaction_type=transaction_type,
            amount=amount,
            fee=0,
            net_amount=amount,
            balance_before=balance_before,
            balance_after=wallet.balance,
            status=TransactionStatus.COMPLETED,
            description=description or f"{transaction_type.value}"
        )
        
        db.add(transaction)
        await db.commit()
        await db.refresh(transaction)
        
        return transaction
    
    @classmethod
    async def process_ride_payment(
        cls,
        db: AsyncSession,
        ride: Ride,
        payment_method: PaymentMethod = PaymentMethod.CASH
    ) -> Payment:
        """Process payment for completed ride"""
        fare = ride.final_fare or ride.estimated_fare
        commission = ride.commission_amount
        driver_amount = fare - commission
        
        # Create payment record
        payment = Payment(
            ride_id=ride.id,
            passenger_id=ride.passenger_id,
            driver_id=ride.driver_id,
            subtotal=fare,
            discount=ride.promo_discount,
            tax=0,
            total=fare,
            commission_rate=ride.commission_percentage,
            commission_amount=commission,
            driver_amount=driver_amount,
            payment_method=payment_method,
            status=TransactionStatus.PENDING
        )
        
        db.add(payment)
        
        # If wallet payment, process deduction
        if payment_method == PaymentMethod.WALLET:
            try:
                await cls.deduct_from_wallet(
                    db,
                    ride.passenger_id,
                    fare,
                    TransactionType.RIDE_PAYMENT,
                    ride_id=ride.id,
                    description=f"Payment for ride {ride.ride_code}"
                )
                payment.status = TransactionStatus.COMPLETED
                payment.paid_at = datetime.utcnow()
            except ValueError as e:
                payment.status = TransactionStatus.FAILED
                raise e
        elif payment_method == PaymentMethod.CASH:
            # Cash payments are marked as completed by driver confirmation
            payment.status = TransactionStatus.COMPLETED
            payment.paid_at = datetime.utcnow()
        
        await db.commit()
        await db.refresh(payment)
        
        return payment
    
    @classmethod
    async def create_withdrawal_request(
        cls,
        db: AsyncSession,
        driver_id: int,
        amount: int
    ) -> WithdrawalRequest:
        """Create withdrawal request for driver"""
        from app.models.driver import Driver
        
        # Get driver with bank details
        query = select(Driver).where(Driver.id == driver_id)
        result = await db.execute(query)
        driver = result.scalar_one_or_none()
        
        if not driver:
            raise ValueError("Driver not found")
        
        if driver.current_balance < amount:
            raise ValueError("Insufficient balance")
        
        if not driver.bank_account_number or not driver.bank_name:
            raise ValueError("Please add bank details before requesting withdrawal")
        
        # Check for minimum withdrawal
        min_withdrawal = 1000  # $10 minimum
        if amount < min_withdrawal:
            raise ValueError(f"Minimum withdrawal amount is {min_withdrawal}")
        
        # Calculate fee (e.g., 1% with minimum $0.50)
        fee = max(int(amount * 0.01), 50)
        net_amount = amount - fee
        
        # Create withdrawal request
        withdrawal = WithdrawalRequest(
            driver_id=driver_id,
            amount=amount,
            fee=fee,
            net_amount=net_amount,
            bank_name=driver.bank_name,
            bank_account_number=driver.bank_account_number[-4:].rjust(len(driver.bank_account_number), '*'),
            bank_routing_number=driver.bank_routing_number,
            bank_account_holder=driver.bank_account_holder,
            status=WithdrawalStatus.PENDING
        )
        
        # Deduct from driver balance (hold)
        driver.current_balance -= amount
        
        db.add(withdrawal)
        await db.commit()
        await db.refresh(withdrawal)
        
        return withdrawal
    
    @classmethod
    async def process_withdrawal(
        cls,
        db: AsyncSession,
        withdrawal_id: int,
        admin_id: int,
        approve: bool,
        rejection_reason: Optional[str] = None
    ) -> WithdrawalRequest:
        """Process withdrawal request (admin action)"""
        query = select(WithdrawalRequest).where(
            WithdrawalRequest.id == withdrawal_id,
            WithdrawalRequest.status == WithdrawalStatus.PENDING
        )
        result = await db.execute(query)
        withdrawal = result.scalar_one_or_none()
        
        if not withdrawal:
            raise ValueError("Withdrawal request not found or already processed")
        
        withdrawal.processed_by = admin_id
        withdrawal.processed_at = datetime.utcnow()
        
        if approve:
            withdrawal.status = WithdrawalStatus.APPROVED
            # In production, initiate actual bank transfer here
            # For now, just mark as completed
            withdrawal.status = WithdrawalStatus.COMPLETED
        else:
            withdrawal.status = WithdrawalStatus.REJECTED
            withdrawal.rejection_reason = rejection_reason
            
            # Refund driver balance
            from app.models.driver import Driver
            driver_query = select(Driver).where(Driver.id == withdrawal.driver_id)
            driver_result = await db.execute(driver_query)
            driver = driver_result.scalar_one_or_none()
            if driver:
                driver.current_balance += withdrawal.amount
        
        await db.commit()
        await db.refresh(withdrawal)
        
        return withdrawal
    
    @classmethod
    async def get_transaction_history(
        cls,
        db: AsyncSession,
        user_id: int,
        page: int = 1,
        limit: int = 20,
        transaction_type: Optional[TransactionType] = None,
        sort_by: str = "created_at",
        order: str = "desc"
    ) -> Tuple[List[Transaction], int]:
        """Get user's transaction history"""
        from app.utils.helpers import build_order_by

        wallet = await cls.get_wallet(db, user_id)
        if not wallet:
            return [], 0
        
        count_query = select(func.count(Transaction.id)).where(Transaction.wallet_id == wallet.id)
        query = select(Transaction).where(Transaction.wallet_id == wallet.id)
        if transaction_type:
            count_query = count_query.where(Transaction.transaction_type == transaction_type)
            query = query.where(Transaction.transaction_type == transaction_type)

        count_result = await db.execute(count_query)
        total = count_result.scalar() or 0

        offset = (page - 1) * limit
        order_clause = build_order_by(Transaction, sort_by, order, {"created_at", "updated_at", "amount", "id"})
        query = query.order_by(order_clause).offset(offset).limit(limit)
        result = await db.execute(query)
        transactions = result.scalars().all()
        
        return list(transactions), total
