<?php

namespace App\Services;

use App\Models\SystemAccount;
use App\Models\Market;
use App\Models\Order;
use App\Models\Trade;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

class MarketMakerService
{
    private SystemAccount $marketMakerAccount;
    private const SPREAD_PERCENTAGE = 0.005; // 0.5% spread
    private const ORDER_SIZE_MIN = 0.001;
    private const ORDER_SIZE_MAX = 1.0;
    private const MAX_ORDERS_PER_MARKET = 10;
    private const REFRESH_INTERVAL = 300; // 5 minutes in seconds

    public function __construct()
    {
        $this->marketMakerAccount = SystemAccount::createAccount(
            SystemAccount::TYPE_MARKET_MAKER,
            'System Market Maker',
            'KES',
            [
                'spread_percentage' => self::SPREAD_PERCENTAGE,
                'min_order_size' => self::ORDER_SIZE_MIN,
                'max_order_size' => self::ORDER_SIZE_MAX,
                'max_orders_per_market' => self::MAX_ORDERS_PER_MARKET,
                'refresh_interval' => self::REFRESH_INTERVAL,
                'active' => true,
            ]
        );
    }

    /**
     * Initialize market maker with initial funds
     */
    public function initialize(float $initialFunds): bool
    {
        try {
            return $this->marketMakerAccount->addFunds($initialFunds, 'Market maker initialization');
        } catch (\Exception $e) {
            Log::error('Market maker initialization failed: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Place liquidity orders for all active markets
     */
    public function placeLiquidityOrders(): void
    {
        if (!$this->marketMakerAccount->getConfig('active', false)) {
            Log::info('Market maker is paused');
            return;
        }

        $markets = Market::active()->get();
        
        foreach ($markets as $market) {
            $this->placeMarketOrders($market);
        }
    }

    /**
     * Place buy/sell orders for a specific market
     */
    private function placeMarketOrders(Market $market): void
    {
        try {
            // Get current market price
            $currentPrice = $this->getCurrentMarketPrice($market->id);
            if (!$currentPrice) {
                Log::warning("No price data available for market {$market->id}");
                return;
            }

            // Cancel existing market maker orders for this market
            $this->cancelExistingOrders($market->id);

            // Calculate spread
            $spread = $currentPrice * $this->marketMakerAccount->getConfig('spread_percentage', self::SPREAD_PERCENTAGE);
            $buyPrice = $currentPrice - ($spread / 2);
            $sellPrice = $currentPrice + ($spread / 2);

            // Place buy order slightly below market
            $buyQuantity = $this->calculateOrderSize($market);
            if ($this->canPlaceOrder($buyQuantity * $buyPrice)) {
                $this->placeOrder($market->id, 'buy', 'limit', $buyPrice, $buyQuantity);
            }

            // Place sell order slightly above market
            $sellQuantity = $this->calculateOrderSize($market);
            if ($this->canPlaceOrder($sellQuantity)) {
                $this->placeOrder($market->id, 'sell', 'limit', $sellPrice, $sellQuantity);
            }

            Log::info("Market maker orders placed for {$market->name}", [
                'buy_price' => $buyPrice,
                'sell_price' => $sellPrice,
                'buy_quantity' => $buyQuantity,
                'sell_quantity' => $sellQuantity,
            ]);

        } catch (\Exception $e) {
            Log::error("Error placing market orders for {$market->name}: " . $e->getMessage());
        }
    }

    /**
     * Get current market price from recent trades
     */
    private function getCurrentMarketPrice(string $marketId): ?float
    {
        $recentTrade = Trade::forMarket($marketId)
            ->latest('executed_at')
            ->first();
            
        return $recentTrade ? $recentTrade->price : null;
    }

    /**
     * Calculate random order size within configured limits
     */
    private function calculateOrderSize(Market $market): float
    {
        $minSize = $this->marketMakerAccount->getConfig('min_order_size', self::ORDER_SIZE_MIN);
        $maxSize = $this->marketMakerAccount->getConfig('max_order_size', self::ORDER_SIZE_MAX);
        
        return round(mt_rand($minSize * 1000, $maxSize * 1000) / 1000, $market->quantity_precision);
    }

    /**
     * Check if market maker has sufficient funds for an order
     */
    private function canPlaceOrder(float $requiredAmount): bool
    {
        return $this->marketMakerAccount->hasSufficientFunds($requiredAmount);
    }

    /**
     * Place an order using the market maker account
     */
    private function placeOrder(string $marketId, string $side, string $type, float $price, float $quantity): void
    {
        try {
            DB::transaction(function () use ($marketId, $side, $type, $price, $quantity) {
                // Create order record
                $order = Order::create([
                    'user_id' => $this->marketMakerAccount->id,
                    'market_id' => $marketId,
                    'side' => $side,
                    'type' => $type,
                    'price' => $price,
                    'quantity' => $quantity,
                    'filled_quantity' => 0,
                    'status' => 'active',
                    'is_system_order' => true,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                // Lock funds
                $requiredAmount = $side === 'buy' ? $price * $quantity : $quantity;
                $this->marketMakerAccount->deductFunds($requiredAmount, "Market maker {$side} order #{$order->id}");

                // Add to order book
                $this->addToOrderBook($order);

                Log::info("Market maker order placed", [
                    'order_id' => $order->id,
                    'market_id' => $marketId,
                    'side' => $side,
                    'price' => $price,
                    'quantity' => $quantity,
                ]);
            });
        } catch (\Exception $e) {
            Log::error("Failed to place market maker order: " . $e->getMessage());
        }
    }

    /**
     * Cancel existing market maker orders for a market
     */
    private function cancelExistingOrders(string $marketId): void
    {
        $existingOrders = Order::where('user_id', $this->marketMakerAccount->id)
            ->where('market_id', $marketId)
            ->where('status', 'active')
            ->get();

        foreach ($existingOrders as $order) {
            $this->cancelOrder($order);
        }
    }

    /**
     * Cancel a specific order and release funds
     */
    private function cancelOrder(Order $order): void
    {
        try {
            DB::transaction(function () use ($order) {
                // Release locked funds
                $lockedAmount = $order->side === 'buy' 
                    ? $order->price * ($order->quantity - $order->filled_quantity)
                    : ($order->quantity - $order->filled_quantity);
                
                $this->marketMakerAccount->addFunds($lockedAmount, "Cancelled order #{$order->id}");

                // Update order status
                $order->update(['status' => 'cancelled']);

                // Remove from order book
                $this->removeFromOrderBook($order);

                Log::info("Market maker order cancelled", ['order_id' => $order->id]);
            });
        } catch (\Exception $e) {
            Log::error("Failed to cancel market maker order {$order->id}: " . $e->getMessage());
        }
    }

    /**
     * Add order to Redis order book
     */
    private function addToOrderBook(Order $order): void
    {
        $redis = Redis::connection();
        $key = "orderbook:{$order->market_id}:{$order->side}";
        $redis->zadd($key, $order->price, $order->id);
    }

    /**
     * Remove order from Redis order book
     */
    private function removeFromOrderBook(Order $order): void
    {
        $redis = Redis::connection();
        $key = "orderbook:{$order->market_id}:{$order->side}";
        $redis->zrem($key, $order->id);
    }

    /**
     * Process filled orders and adjust positions
     */
    public function processFilledOrders(): void
    {
        $filledOrders = Order::where('user_id', $this->marketMakerAccount->id)
            ->where('status', 'filled')
            ->where('processed_for_mm', false)
            ->get();

        foreach ($filledOrders as $order) {
            $this->handleFilledOrder($order);
        }
    }

    /**
     * Handle a filled market maker order
     */
    private function handleFilledOrder(Order $order): void
    {
        try {
            // Calculate profit/loss
            $trade = Trade::where(function ($query) use ($order) {
                $query->where('buy_order_id', $order->id)
                      ->orWhere('sell_order_id', $order->id);
            })->first();

            if ($trade) {
                $profit = $this->calculateProfit($order, $trade);
                Log::info("Market maker order filled", [
                    'order_id' => $order->id,
                    'trade_id' => $trade->id,
                    'profit' => $profit,
                ]);
            }

            // Mark as processed
            $order->update(['processed_for_mm' => true]);

        } catch (\Exception $e) {
            Log::error("Error processing filled market maker order {$order->id}: " . $e->getMessage());
        }
    }

    /**
     * Calculate profit/loss for a filled order
     */
    private function calculateProfit(Order $order, Trade $trade): float
    {
        if ($order->side === 'buy') {
            // Buy order filled - we now have assets
            return ($trade->quantity * $trade->price) - ($order->price * $order->quantity);
        } else {
            // Sell order filled - we now have currency
            return ($order->price * $order->quantity) - ($trade->quantity * $trade->price);
        }
    }

    /**
     * Get market maker statistics
     */
    public function getStatistics(): array
    {
        $activeOrders = Order::where('user_id', $this->marketMakerAccount->id)
            ->where('status', 'active')
            ->count();

        $filledOrders = Order::where('user_id', $this->marketMakerAccount->id)
            ->where('status', 'filled')
            ->count();

        $totalTrades = Trade::whereHas('buyOrder', function ($query) {
            $query->where('user_id', $this->marketMakerAccount->id);
        })->orWhereHas('sellOrder', function ($query) {
            $query->where('user_id', $this->marketMakerAccount->id);
        })->count();

        return [
            'account_balance' => $this->marketMakerAccount->getBalance(),
            'active_orders' => $activeOrders,
            'filled_orders' => $filledOrders,
            'total_trades' => $totalTrades,
            'account_status' => $this->marketMakerAccount->status,
            'configurations' => $this->marketMakerAccount->configurations,
        ];
    }

    /**
     * Pause market maker operations
     */
    public function pause(): void
    {
        $this->marketMakerAccount->setConfig('active', false);
        $this->marketMakerAccount->pause();
        Log::info('Market maker paused');
    }

    /**
     * Resume market maker operations
     */
    public function resume(): void
    {
        $this->marketMakerAccount->setConfig('active', true);
        $this->marketMakerAccount->activate();
        Log::info('Market maker resumed');
    }

    /**
     * Adjust market maker parameters
     */
    public function updateConfiguration(array $config): bool
    {
        try {
            foreach ($config as $key => $value) {
                $this->marketMakerAccount->setConfig($key, $value);
            }
            return true;
        } catch (\Exception $e) {
            Log::error('Failed to update market maker configuration: ' . $e->getMessage());
            return false;
        }
    }
}