<?php

namespace App\Services;

use App\Models\Market;
use App\Models\Order;
use App\Models\Trade;
use App\Models\Wallet;
use App\Models\LedgerTransaction;
use Illuminate\Support\Facades\DB;
use App\Services\WebSocketBroadcastingService;
use App\Services\RealTimeSyncService;

class OrderMatchingService
{
    /**
     * Process an incoming order and attempt to match with existing orders
     */
    public function processOrder(Order $incomingOrder): void
    {
        // Fetch matching orders based on market and side
        $matchingOrders = $this->getMatchingOrders($incomingOrder);
        
        foreach ($matchingOrders as $matchingOrder) {
            // Check if the orders can be matched (price compatibility)
            if ($this->isMatchPossible($incomingOrder, $matchingOrder)) {
                // Calculate possible fill quantities
                $fillQuantities = $this->calculateFillQuantities($incomingOrder, $matchingOrder);
                
                if ($fillQuantities['canFill']) {
                    // Execute the trade
                    $this->executeTrade($incomingOrder, $matchingOrder, $fillQuantities['price'], $fillQuantities['quantity']);
                    
                    // Update order statuses
                    $this->updateOrderStatus($incomingOrder, $fillQuantities['quantity']);
                    $this->updateOrderStatus($matchingOrder, $fillQuantities['quantity']);
                    
                    // Check if either order is fully filled
                    if ($incomingOrder->isFilled() || $matchingOrder->isFilled()) {
                        break; // Stop processing if one order is fully filled
                    }
                }
            }
        }
    }

    /**
     * Get matching orders for an incoming order
     */
    private function getMatchingOrders(Order $order)
    {
        $oppositeSide = $order->side === 'buy' ? 'sell' : 'buy';
        
        $query = Order::where('market_id', $order->market_id)
                      ->where('side', $oppositeSide)
                      ->where('status', 'pending')
                      ->orderBy('price', $order->side === 'buy' ? 'asc' : 'desc') // Lowest sell price for buy orders, highest buy price for sell orders
                      ->orderBy('created_at', 'asc'); // Price-time priority
        
        // For market orders, we may want to limit the number of potential matches
        if ($order->type === 'market') {
            $query->limit(10); // Limit for market orders
        }
        
        return $query->get();
    }

    /**
     * Check if two orders can be matched
     */
    private function isMatchPossible(Order $order1, Order $order2): bool
    {
        // For a buy order to match with a sell order:
        // buy_price >= sell_price
        if ($order1->side === 'buy' && $order2->side === 'sell') {
            return $order1->price >= $order2->price;
        }
        
        // For a sell order to match with a buy order:
        // sell_price <= buy_price
        if ($order1->side === 'sell' && $order2->side === 'buy') {
            return $order1->price <= $order2->price;
        }
        
        return false;
    }

    /**
     * Calculate fill quantities for two orders
     */
    private function calculateFillQuantities(Order $order1, Order $order2): array
    {
        $remainingQuantity1 = $order1->quantity - $order1->filled_quantity;
        $remainingQuantity2 = $order2->quantity - $order2->filled_quantity;
        
        $fillQuantity = min($remainingQuantity1, $remainingQuantity2);
        
        // Use the resting order's price (the one that was placed earlier)
        $executionPrice = $order1->created_at->lt($order2->created_at) ? $order2->price : $order1->price;
        
        return [
            'canFill' => $fillQuantity > 0,
            'quantity' => $fillQuantity,
            'price' => $executionPrice,
        ];
    }

    /**
     * Execute a trade between two orders
     */
    private function executeTrade(Order $buyOrder, Order $sellOrder, float $price, float $quantity): void
    {
        DB::transaction(function () use ($buyOrder, $sellOrder, $price, $quantity) {
            // Create the trade record
            $trade = Trade::createTrade($buyOrder->id, $sellOrder->id, $buyOrder->market_id, $price, $quantity);
            
            // Perform settlements
            $this->performSettlement($buyOrder, $sellOrder, $trade);
            
            // Broadcast the trade execution
            $broadcastingService = new WebSocketBroadcastingService();
            $broadcastingService->broadcastTrade($trade);
            
            // Synchronize across all platforms
            $syncService = new RealTimeSyncService();
            $syncService->syncTrade($trade);
        });
    }

    /**
     * Perform settlement for a trade
     */
    private function performSettlement(Order $buyOrder, Order $sellOrder, Trade $trade): void
    {
        $market = $buyOrder->market;
        
        // Calculate fees
        $tradeValue = $trade->price * $trade->quantity;
        $buyFee = $tradeValue * $market->taker_fee_rate; // Assuming buy order is taker
        $sellFee = $tradeValue * $market->maker_fee_rate; // Assuming sell order is maker
        
        // Buyer debit (quote currency) = trade_value + fee
        $this->createLedgerTransaction(
            $buyOrder->user_id,
            'trade_buy_debit',
            $tradeValue + $buyFee,
            $market->quote_currency,
            "Trade purchase: {$trade->quantity} {$market->base_currency} @ {$trade->price}",
            ['trade_id' => $trade->id]
        );
        
        // Buyer credit (base currency) = quantity
        $this->createLedgerTransaction(
            $buyOrder->user_id,
            'trade_buy_credit',
            $trade->quantity,
            $market->base_currency,
            "Trade purchase credit: {$trade->quantity} {$market->base_currency}",
            ['trade_id' => $trade->id]
        );
        
        // Seller credit (quote currency) = trade_value - fee
        $this->createLedgerTransaction(
            $sellOrder->user_id,
            'trade_sell_credit',
            $tradeValue - $sellFee,
            $market->quote_currency,
            "Trade sale: {$trade->quantity} {$market->base_currency} @ {$trade->price}",
            ['trade_id' => $trade->id]
        );
        
        // Seller debit (base currency) = quantity
        $this->createLedgerTransaction(
            $sellOrder->user_id,
            'trade_sell_debit',
            $trade->quantity,
            $market->base_currency,
            "Trade sale debit: {$trade->quantity} {$market->base_currency}",
            ['trade_id' => $trade->id]
        );
        
        // Platform credit (fee) = total fee amount
        $this->createLedgerTransaction(
            'platform_account', // Special platform account ID
            'platform_fee',
            $buyFee + $sellFee,
            $market->quote_currency,
            "Platform fees from trade: {$trade->id}",
            ['trade_id' => $trade->id]
        );
        
        // Update wallet locks to reflect consumed amounts
        $this->consumeWalletLocks($buyOrder, $trade->price * $trade->quantity + $buyFee);
        $this->consumeWalletLocks($sellOrder, $trade->quantity);
    }

    /**
     * Create a ledger transaction
     */
    private function createLedgerTransaction($userId, string $type, float $amount, string $currency, string $description, array $details = []): void
    {
        $transaction = LedgerTransaction::create([
            'user_id' => $userId,
            'type' => $type,
            'description' => $description,
            'amount' => $amount,
            'currency' => $currency,
            'status' => 'completed',
            'details' => $details,
        ]);
    }

    /**
     * Consume wallet locks based on trade execution
     */
    private function consumeWalletLocks(Order $order, float $consumedAmount): void
    {
        // Get all active locks for this order
        $locks = $order->walletLocks()->active()->get();
        
        $remainingToConsume = $consumedAmount;
        
        foreach ($locks as $lock) {
            if ($remainingToConsume <= 0) {
                break;
            }
            
            $availableToConsume = $lock->amount_locked - $lock->amount_consumed;
            $amountToConsume = min($remainingToConsume, $availableToConsume);
            
            $lock->amount_consumed += $amountToConsume;
            if ($lock->amount_consumed >= $lock->amount_locked) {
                $lock->status = 'released';
            }
            $lock->save();
            
            $remainingToConsume -= $amountToConsume;
        }
    }

    /**
     * Update order status after filling
     */
    private function updateOrderStatus(Order $order, float $quantityFilled): void
    {
        $order->updateStatus($quantityFilled);
    }

    /**
     * Process market orders with multi-level consumption
     */
    public function processMarketOrder(Order $marketOrder): void
    {
        // For market orders, we need to consume multiple price levels
        $matchingOrders = $this->getMatchingOrders($marketOrder);
        
        $remainingQuantity = $marketOrder->quantity - $marketOrder->filled_quantity;
        
        foreach ($matchingOrders as $matchingOrder) {
            if ($remainingQuantity <= 0) {
                break;
            }
            
            $fillQuantities = $this->calculateFillQuantities($marketOrder, $matchingOrder);
            
            if ($fillQuantities['canFill']) {
                // Adjust fill quantity if it exceeds remaining quantity
                $actualFillQuantity = min($fillQuantities['quantity'], $remainingQuantity);
                
                // Execute the trade
                $this->executeTrade($marketOrder, $matchingOrder, $fillQuantities['price'], $actualFillQuantity);
                
                // Update order statuses
                $this->updateOrderStatus($marketOrder, $actualFillQuantity);
                $this->updateOrderStatus($matchingOrder, $actualFillQuantity);
                
                $remainingQuantity -= $actualFillQuantity;
                
                // Check if the matching order is fully filled
                if ($matchingOrder->isFilled()) {
                    continue; // Continue to next matching order
                }
            }
        }
        
        // If market order is not fully filled, it becomes a limit order at market price?
        // Or remain as unfilled market order - depends on implementation
    }
}