<?php

require_once __DIR__ . '/../config/database.php';
if (file_exists(__DIR__ . '/SystemConfigService.php')) {
    require_once __DIR__ . '/SystemConfigService.php';
}
if (file_exists(__DIR__ . '/TradingStyleService.php')) {
    require_once __DIR__ . '/TradingStyleService.php';
}

/*
|--------------------------------------------------------------------------
| DynamicExecutionService
|--------------------------------------------------------------------------
| Final DB-driven institutional execution engine.
|
| Called only at final alert stage, after:
| - institutional zone reached
| - liquidity sweep detected
| - BOS/CHoCH confirmed
|
| It recalculates entry, SL, TP1, TP2 and TP3 from CURRENT candles using:
| - trading_style_settings table
| - selected user trading style
| - asset-specific SL pips
| - asset-specific RR targets
| - ATR multiplier
| - risk buffer percent
| - symbol asset type
|--------------------------------------------------------------------------
*/

class DynamicExecutionService
{
    public static function buildFromAlert(array $alert, array $market = []): array
    {
        $symbol = strtoupper(trim((string)($alert['symbol'] ?? '')));
        $timeframe = trim((string)($alert['timeframe'] ?? ''));
        $side = strtoupper(trim((string)($alert['side'] ?? '')));
        $styleKey = strtolower(trim((string)($alert['style'] ?? 'intraday')));

        if ($symbol === '' || $timeframe === '' || !in_array($side, ['BUY', 'SELL'], true)) {
            return self::fallbackFromStoredAlert($alert, 'Invalid alert data for dynamic execution.');
        }

        $assetClass = self::assetClass($symbol);
        $style = self::loadStyleSettings($styleKey);

        if (!$style) {
            return self::fallbackFromStoredAlert($alert, 'Trading style settings could not be loaded.');
        }

        $candles = self::getRecentCandles($symbol, $timeframe, 120);

        if (count($candles) < 15) {
            return self::fallbackFromStoredAlert($alert, 'Not enough candles to dynamically recalculate execution.');
        }

        $latest = end($candles);
        $currentPrice = self::num($market['current_price'] ?? ($latest['close_price'] ?? null));

        if ($currentPrice === null) {
            return self::fallbackFromStoredAlert($alert, 'No current price available for dynamic execution.');
        }

        $pipSize = self::pipSize($symbol, $assetClass);
        $styleSlPips = self::styleSlPips($style, $assetClass, $symbol);
        $tpRR = self::styleTakeProfitRR($style, $assetClass);

        $atr = self::atr($candles, 14);
        $atrMultiplier = self::num($style['atr_multiplier'] ?? 1.00) ?: 1.00;
        $riskBufferPercent = self::num($style['risk_buffer_percent'] ?? 10.00) ?: 10.00;

        $sweep = self::findLatestSweep(
            $candles,
            $side,
            self::num($alert['liquidity_sweep_level'] ?? null),
            (int)($style['sweep_strength'] ?? 5)
        );

        $structure = self::findStructureConfirmation(
            $candles,
            $side,
            (int)($style['bos_strength'] ?? 5)
        );

        /*
        |--------------------------------------------------------------------------
        | Risk model
        |--------------------------------------------------------------------------
        | 1. Base SL distance comes from trading_style_settings asset-specific pips.
        | 2. ATR multiplier expands/contracts the dynamic risk.
        | 3. risk_buffer_percent adds protection beyond the sweep.
        |--------------------------------------------------------------------------
        */

        $baseSlDistance = max($styleSlPips * $pipSize, self::minimumDistance($symbol, $assetClass));
        $atrDistance = max(0.0, $atr * $atrMultiplier);
        $rawRiskDistance = max($baseSlDistance, $atrDistance);

        $bufferDistance = $rawRiskDistance * ($riskBufferPercent / 100);
        $finalRiskDistance = $rawRiskDistance + $bufferDistance;

        if ($finalRiskDistance <= 0) {
            return self::fallbackFromStoredAlert($alert, 'Invalid final risk distance.');
        }

        /*
        |--------------------------------------------------------------------------
        | Post-confirmation entry
        |--------------------------------------------------------------------------
        | Entry is calculated at the time of alert confirmation, not at analysis time.
        |--------------------------------------------------------------------------
        */

        $entry = $currentPrice;

        if ($side === 'BUY') {
            $sweepLow = self::num($sweep['low_price'] ?? null);

            if ($sweepLow !== null) {
                $stopLoss = min($entry - $finalRiskDistance, $sweepLow - $bufferDistance);
            } else {
                $stopLoss = $entry - $finalRiskDistance;
            }

            $riskDistance = abs($entry - $stopLoss);

            $tp1 = $entry + ($riskDistance * $tpRR['tp1']);
            $tp2 = $entry + ($riskDistance * $tpRR['tp2']);
            $tp3 = $entry + ($riskDistance * $tpRR['tp3']);

            $entryLow = $entry - ($riskDistance * self::entryZoneFactor($style));
            $entryHigh = $entry + ($riskDistance * self::entryZoneFactor($style));
        } else {
            $sweepHigh = self::num($sweep['high_price'] ?? null);

            if ($sweepHigh !== null) {
                $stopLoss = max($entry + $finalRiskDistance, $sweepHigh + $bufferDistance);
            } else {
                $stopLoss = $entry + $finalRiskDistance;
            }

            $riskDistance = abs($stopLoss - $entry);

            $tp1 = $entry - ($riskDistance * $tpRR['tp1']);
            $tp2 = $entry - ($riskDistance * $tpRR['tp2']);
            $tp3 = $entry - ($riskDistance * $tpRR['tp3']);

            $entryLow = $entry - ($riskDistance * self::entryZoneFactor($style));
            $entryHigh = $entry + ($riskDistance * self::entryZoneFactor($style));
        }

        if ($riskDistance <= 0) {
            return self::fallbackFromStoredAlert($alert, 'Invalid calculated risk distance.');
        }

        return [
            'ok' => true,
            'dynamic' => true,
            'source' => 'db_driven_dynamic_execution_after_sweep_bos',
            'reason' => 'Entry, SL and TPs recalculated after liquidity sweep and BOS/CHoCH using trading_style_settings.',
            'symbol' => $symbol,
            'timeframe' => $timeframe,
            'side' => $side,
            'asset_class' => $assetClass,
            'style_key' => $style['style_key'] ?? $styleKey,
            'style_name' => $style['style_name'] ?? $styleKey,
            'current_price' => self::roundPrice($currentPrice, $symbol, $assetClass),
            'entry_price' => self::roundPrice($entry, $symbol, $assetClass),
            'entry_low' => self::roundPrice(min($entryLow, $entryHigh), $symbol, $assetClass),
            'entry_high' => self::roundPrice(max($entryLow, $entryHigh), $symbol, $assetClass),
            'stop_loss' => self::roundPrice($stopLoss, $symbol, $assetClass),
            'tp1' => self::roundPrice($tp1, $symbol, $assetClass),
            'tp2' => self::roundPrice($tp2, $symbol, $assetClass),
            'tp3' => self::roundPrice($tp3, $symbol, $assetClass),
            'risk_distance' => self::roundPrice($riskDistance, $symbol, $assetClass),
            'style_sl_pips' => $styleSlPips,
            'pip_size' => $pipSize,
            'base_sl_distance' => self::roundPrice($baseSlDistance, $symbol, $assetClass),
            'atr' => self::roundPrice($atr, $symbol, $assetClass),
            'atr_multiplier' => $atrMultiplier,
            'atr_distance' => self::roundPrice($atrDistance, $symbol, $assetClass),
            'risk_buffer_percent' => $riskBufferPercent,
            'buffer_distance' => self::roundPrice($bufferDistance, $symbol, $assetClass),
            'rr1' => $tpRR['tp1'],
            'rr2' => $tpRR['tp2'],
            'rr3' => $tpRR['tp3'],
            'min_rr' => self::num($style['min_rr'] ?? 1.50),
            'sweep_strength' => (int)($style['sweep_strength'] ?? 5),
            'bos_strength' => (int)($style['bos_strength'] ?? 5),
            'confirmation_strictness' => $style['confirmation_strictness'] ?? 'MEDIUM',
            'sweep_time' => $sweep['open_time'] ?? null,
            'structure_time' => $structure['open_time'] ?? null,
        ];
    }

    private static function loadStyleSettings(string $styleKey): ?array
    {
        $styleKey = strtolower(trim($styleKey));
        if ($styleKey === '') {
            $styleKey = 'intraday';
        }

        try {
            if (class_exists('TradingStyleService') && method_exists('TradingStyleService', 'ensureSchema')) {
                TradingStyleService::ensureSchema();
            }

            $stmt = db()->prepare("
                SELECT *
                FROM trading_style_settings
                WHERE LOWER(style_key) = LOWER(?)
                  AND is_active = 1
                LIMIT 1
            ");
            $stmt->execute([$styleKey]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                return $row;
            }

            $stmt = db()->prepare("
                SELECT *
                FROM trading_style_settings
                WHERE LOWER(style_key) = 'intraday'
                  AND is_active = 1
                LIMIT 1
            ");
            $stmt->execute();
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                return $row;
            }

            $stmt = db()->query("
                SELECT *
                FROM trading_style_settings
                WHERE is_active = 1
                ORDER BY sort_order ASC, id ASC
                LIMIT 1
            ");
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }

    private static function styleSlPips(array $style, string $assetClass, string $symbol = ''): float
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        $styleKey = strtolower((string)($style['style_key'] ?? 'intraday'));

        if ($assetClass === 'CRYPTO') {
            if (class_exists('TradingStyleService') && method_exists('TradingStyleService', 'cryptoStopPipsForSymbol')) {
                return (float)TradingStyleService::cryptoStopPipsForSymbol($symbol, $styleKey);
            }

            if (strpos($symbol, 'BTC') === 0) {
                if ($styleKey === 'scalping') return 200;
                if ($styleKey === 'swing') return 1500;
                if ($styleKey === 'auto') return 600;
                return 500;
            }

            if (strpos($symbol, 'ETH') === 0) {
                if ($styleKey === 'scalping') return 10;
                if ($styleKey === 'swing') return 75;
                if ($styleKey === 'auto') return 30;
                return 25;
            }

            if ($styleKey === 'scalping') return 25;
            if ($styleKey === 'swing') return 150;
            if ($styleKey === 'auto') return 60;
            return 50;
        }

        $map = [
            'FOREX' => 'forex_sl_pips',
            'GOLD' => 'gold_sl_pips',
            'SILVER' => 'silver_sl_pips',
            'METAL' => 'silver_sl_pips',
            'INDICES' => 'indices_sl_pips',
            'COMMODITIES' => 'commodities_sl_pips',
            'STOCKS' => 'stocks_sl_pips',
        ];

        $column = $map[$assetClass] ?? 'forex_sl_pips';
        $value = self::num($style[$column] ?? null);

        if ($value && $value > 0) {
            return $value;
        }

        if ($assetClass === 'SILVER' || $assetClass === 'METAL') {
            return self::num($style['silver_sl_pips'] ?? null) ?: 100;
        }

        if ($assetClass === 'GOLD') {
            return self::num($style['gold_sl_pips'] ?? null) ?: 300;
        }

        return self::num($style['forex_sl_pips'] ?? null) ?: 30;
    }


    private static function styleTakeProfitRR(array $style, string $assetClass): array
    {
        $prefixMap = [
            'FOREX' => 'forex',
            'GOLD' => 'gold',
            'SILVER' => 'silver',
            'METAL' => 'silver',
            'CRYPTO' => 'crypto',
            'INDICES' => 'indices',
            'COMMODITIES' => 'commodities',
            'STOCKS' => 'stocks',
        ];

        $prefix = $prefixMap[$assetClass] ?? 'forex';

        $tp1 = self::num($style[$prefix . '_tp1_rr'] ?? null);
        $tp2 = self::num($style[$prefix . '_tp2_rr'] ?? null);
        $tp3 = self::num($style[$prefix . '_tp3_rr'] ?? null);

        return [
            'tp1' => $tp1 ?: 1.0,
            'tp2' => $tp2 ?: 2.0,
            'tp3' => $tp3 ?: 3.0,
        ];
    }


    private static function assetClass(string $symbol): string
    {
        $symbol = strtoupper(trim($symbol));

        try {
            $stmt = db()->prepare("
                SELECT asset_type
                FROM symbols
                WHERE UPPER(symbol) = UPPER(?)
                LIMIT 1
            ");
            $stmt->execute([$symbol]);
            $assetType = strtoupper(trim((string)$stmt->fetchColumn()));

            if ($assetType !== '') {
                if (in_array($assetType, ['METAL', 'METALS'], true)) {
                    if (str_contains($symbol, 'XAU') || str_contains($symbol, 'GOLD')) {
                        return 'GOLD';
                    }
                    return 'SILVER';
                }

                if (in_array($assetType, ['INDEX', 'INDICES', 'INDEXES'], true)) {
                    return 'INDICES';
                }

                if (in_array($assetType, ['CRYPTO', 'CRYPTOCURRENCY'], true)) {
                    return 'CRYPTO';
                }

                if (in_array($assetType, ['COMMODITY', 'COMMODITIES'], true)) {
                    return 'COMMODITIES';
                }

                if (in_array($assetType, ['STOCK', 'STOCKS'], true)) {
                    return 'STOCKS';
                }

                if ($assetType === 'FOREX') {
                    return 'FOREX';
                }
            }
        } catch (Throwable $e) {
        }

        if (str_contains($symbol, 'BTC') || str_contains($symbol, 'ETH') || str_contains($symbol, 'SOL')) {
            return 'CRYPTO';
        }

        if (str_contains($symbol, 'XAU') || str_contains($symbol, 'GOLD')) {
            return 'GOLD';
        }

        if (str_contains($symbol, 'XAG') || str_contains($symbol, 'SILVER')) {
            return 'SILVER';
        }

        if (str_contains($symbol, 'NAS') || str_contains($symbol, 'US30') || str_contains($symbol, 'SPX') || str_contains($symbol, 'US100')) {
            return 'INDICES';
        }

        return 'FOREX';
    }

    private static function pipSize(string $symbol, string $assetClass): float
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));

        try {
            if (class_exists('SystemConfigService') && method_exists('SystemConfigService', 'getPipSize')) {
                $pipSize = (float)SystemConfigService::getPipSize($symbol);
                if ($pipSize > 0) {
                    return $pipSize;
                }
            }
        } catch (Throwable $e) {
        }

        if ($assetClass === 'GOLD' || str_contains($symbol, 'XAU')) {
            return 0.1;
        }

        if ($assetClass === 'SILVER' || str_contains($symbol, 'XAG')) {
            return 0.01;
        }

        if ($assetClass === 'CRYPTO') {
            return 1.0;
        }

        if ($assetClass === 'INDICES') {
            return 1.0;
        }

        if ($assetClass === 'COMMODITIES') {
            return 0.01;
        }

        if ($assetClass === 'STOCKS') {
            return 0.01;
        }

        if (str_contains($symbol, 'JPY')) {
            return 0.01;
        }

        return 0.0001;
    }


    private static function minimumDistance(string $symbol, string $assetClass): float
    {
        $pip = self::pipSize($symbol, $assetClass);

        if ($assetClass === 'CRYPTO') {
            return max(10.0, $pip * 10);
        }

        if ($assetClass === 'INDICES') {
            return 2.0;
        }

        if ($assetClass === 'GOLD') {
            return 0.50;
        }

        if ($assetClass === 'SILVER') {
            return 0.05;
        }

        return $pip * 5;
    }


    private static function entryZoneFactor(array $style): float
    {
        $strictness = strtoupper((string)($style['confirmation_strictness'] ?? 'MEDIUM'));

        if ($strictness === 'HIGH') {
            return 0.05;
        }

        if ($strictness === 'LOW') {
            return 0.12;
        }

        return 0.08;
    }

    private static function getRecentCandles(string $symbol, string $timeframe, int $limit = 120): array
    {
        try {
            $stmt = db()->prepare("
                SELECT *
                FROM candles
                WHERE symbol = ?
                  AND timeframe = ?
                ORDER BY open_time DESC
                LIMIT ?
            ");
            $stmt->bindValue(1, $symbol);
            $stmt->bindValue(2, $timeframe);
            $stmt->bindValue(3, $limit, PDO::PARAM_INT);
            $stmt->execute();

            return array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC) ?: []);
        } catch (Throwable $e) {
            return [];
        }
    }

    private static function findLatestSweep(array $candles, string $side, $configuredSweepLevel = null, int $strength = 5): array
    {
        $lookback = max(5, min(20, $strength + 5));
        $recent = array_slice($candles, -30);

        for ($i = count($recent) - 1; $i >= 3; $i--) {
            $current = $recent[$i];
            $previous = array_slice($recent, max(0, $i - $lookback), min($lookback, $i));

            if (!$previous) {
                continue;
            }

            $prevHigh = max(array_map(fn($c) => (float)($c['high_price'] ?? 0), $previous));
            $prevLow = min(array_map(fn($c) => (float)($c['low_price'] ?? PHP_FLOAT_MAX), $previous));

            $high = (float)($current['high_price'] ?? 0);
            $low = (float)($current['low_price'] ?? 0);

            if ($side === 'BUY') {
                if ($configuredSweepLevel !== null && $low <= (float)$configuredSweepLevel) {
                    return $current;
                }

                if ($low < $prevLow) {
                    return $current;
                }
            }

            if ($side === 'SELL') {
                if ($configuredSweepLevel !== null && $high >= (float)$configuredSweepLevel) {
                    return $current;
                }

                if ($high > $prevHigh) {
                    return $current;
                }
            }
        }

        return end($recent) ?: [];
    }

    private static function findStructureConfirmation(array $candles, string $side, int $strength = 5): array
    {
        $lookback = max(5, min(20, $strength + 5));
        $recent = array_slice($candles, -30);

        for ($i = count($recent) - 1; $i >= 5; $i--) {
            $current = $recent[$i];
            $previous = array_slice($recent, max(0, $i - $lookback), min($lookback, $i));

            if (!$previous) {
                continue;
            }

            $prevHigh = max(array_map(fn($c) => (float)($c['high_price'] ?? 0), $previous));
            $prevLow = min(array_map(fn($c) => (float)($c['low_price'] ?? PHP_FLOAT_MAX), $previous));
            $close = (float)($current['close_price'] ?? 0);

            if ($side === 'BUY' && $close > $prevHigh) {
                return $current;
            }

            if ($side === 'SELL' && $close < $prevLow) {
                return $current;
            }
        }

        return end($recent) ?: [];
    }

    private static function atr(array $candles, int $period = 14): float
    {
        if (count($candles) < 2) {
            return 0.0;
        }

        $trs = [];

        for ($i = 1; $i < count($candles); $i++) {
            $high = (float)($candles[$i]['high_price'] ?? 0);
            $low = (float)($candles[$i]['low_price'] ?? 0);
            $prevClose = (float)($candles[$i - 1]['close_price'] ?? 0);

            $trs[] = max(
                $high - $low,
                abs($high - $prevClose),
                abs($low - $prevClose)
            );
        }

        $trs = array_slice($trs, -$period);

        return $trs ? array_sum($trs) / count($trs) : 0.0;
    }

    private static function fallbackFromStoredAlert(array $alert, string $reason): array
    {
        return [
            'ok' => false,
            'dynamic' => false,
            'source' => 'stored_alert_fallback',
            'reason' => $reason,
            'symbol' => $alert['symbol'] ?? '',
            'timeframe' => $alert['timeframe'] ?? '',
            'side' => $alert['side'] ?? '',
            'style_key' => $alert['style'] ?? '',
            'current_price' => null,
            'entry_price' => self::num($alert['entry_price'] ?? null),
            'entry_low' => self::num($alert['entry_low'] ?? null),
            'entry_high' => self::num($alert['entry_high'] ?? null),
            'stop_loss' => self::num($alert['stop_loss'] ?? null),
            'tp1' => self::num($alert['tp1'] ?? null),
            'tp2' => self::num($alert['tp2'] ?? null),
            'tp3' => self::num($alert['tp3'] ?? null),
        ];
    }

    private static function roundPrice($price, string $symbol, string $assetClass): ?float
    {
        if (!is_numeric($price)) {
            return null;
        }

        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));

        try {
            if (class_exists('SystemConfigService') && method_exists('SystemConfigService', 'priceDecimals')) {
                return round((float)$price, (int)SystemConfigService::priceDecimals($symbol));
            }
        } catch (Throwable $e) {
        }

        if ($assetClass === 'CRYPTO') {
            return round((float)$price, 2);
        }

        if ($assetClass === 'INDICES') {
            return round((float)$price, 2);
        }

        if ($assetClass === 'GOLD' || str_contains($symbol, 'XAU')) {
            return round((float)$price, 2);
        }

        if ($assetClass === 'SILVER' || str_contains($symbol, 'XAG')) {
            return round((float)$price, 3);
        }

        if (str_contains($symbol, 'JPY')) {
            return round((float)$price, 3);
        }

        return round((float)$price, 5);
    }


    private static function num($v)
    {
        return is_numeric($v) ? (float)$v : null;
    }
}
