<?php

namespace App\Services\MarketData;

class GreeksCalculator
{
    public function __construct(private float $riskFreeRate = 0.065) {}

    public function calculate(
        float $spot,
        float $strike,
        float $optionPrice,
        float $timeToExpiryYears,
        string $optionType
    ): array {
        $iv = $this->impliedVolatility($spot, $strike, $optionPrice, $timeToExpiryYears, $optionType);
        $iv = max(0.01, min($iv, 3.0));

        $d1 = $this->d1($spot, $strike, $timeToExpiryYears, $iv);
        $d2 = $d1 - $iv * sqrt(max($timeToExpiryYears, 1 / 365 / 24));

        $isCall = strtoupper($optionType) === 'CE';
        $delta = $isCall ? $this->normCdf($d1) : $this->normCdf($d1) - 1;
        $gamma = $this->normPdf($d1) / max($spot * $iv * sqrt(max($timeToExpiryYears, 1e-6)), 1e-6);
        $theta = $this->theta($spot, $strike, $timeToExpiryYears, $iv, $d1, $d2, $isCall);
        $vega = $spot * $this->normPdf($d1) * sqrt(max($timeToExpiryYears, 1e-6)) / 100;

        return [
            'iv' => round($iv * 100, 4),
            'delta' => round($delta, 6),
            'gamma' => round($gamma, 6),
            'theta' => round($theta, 6),
            'vega' => round($vega, 6),
        ];
    }

    public function score(array $greek, array $tick, array $weights): float
    {
        $deltaScore = abs((float) ($greek['delta'] ?? 0)) * 100;
        $gammaScore = ((float) ($greek['gamma'] ?? 0)) * 10000;
        $oiScore = min(100, log10(max((int) ($tick['oi'] ?? 1), 1)) * 20);
        $volumeScore = min(100, log10(max((int) ($tick['volume'] ?? 1), 1)) * 15);
        $ivScore = min(100, (float) ($greek['iv'] ?? 0));
        $spread = max(0.0, (float) ($tick['ask_price'] ?? 0) - (float) ($tick['bid_price'] ?? 0));
        $liquidityScore = max(0, 100 - ($spread * 10));

        return round(
            ($deltaScore * ($weights['delta'] ?? 25) / 100) +
            ($gammaScore * ($weights['gamma'] ?? 25) / 100) +
            ($oiScore * ($weights['oi_change'] ?? 20) / 100) +
            ($volumeScore * ($weights['volume_change'] ?? 15) / 100) +
            ($ivScore * ($weights['iv_change'] ?? 10) / 100) +
            ($liquidityScore * ($weights['liquidity'] ?? 5) / 100),
            2
        );
    }

    private function impliedVolatility(
        float $spot,
        float $strike,
        float $price,
        float $time,
        string $optionType,
        int $maxIterations = 40
    ): float {
        $sigma = 0.2;
        $isCall = strtoupper($optionType) === 'CE';

        for ($i = 0; $i < $maxIterations; $i++) {
            $model = $this->blackScholes($spot, $strike, $time, $sigma, $isCall);
            $vega = $spot * $this->normPdf($this->d1($spot, $strike, $time, $sigma)) * sqrt(max($time, 1e-6));

            if (abs($vega) < 1e-8) {
                break;
            }

            $sigma -= ($model - $price) / $vega;
            $sigma = max(0.0001, $sigma);
        }

        return $sigma;
    }

    private function blackScholes(float $spot, float $strike, float $time, float $sigma, bool $isCall): float
    {
        $d1 = $this->d1($spot, $strike, $time, $sigma);
        $d2 = $d1 - $sigma * sqrt(max($time, 1e-6));

        if ($isCall) {
            return $spot * $this->normCdf($d1) - $strike * exp(-$this->riskFreeRate * $time) * $this->normCdf($d2);
        }

        return $strike * exp(-$this->riskFreeRate * $time) * $this->normCdf(-$d2) - $spot * $this->normCdf(-$d1);
    }

    private function theta(float $s, float $k, float $t, float $sigma, float $d1, float $d2, bool $isCall): float
    {
        $first = -($s * $this->normPdf($d1) * $sigma) / (2 * sqrt(max($t, 1e-6)));
        if ($isCall) {
            return ($first - $this->riskFreeRate * $k * exp(-$this->riskFreeRate * $t) * $this->normCdf($d2)) / 365;
        }

        return ($first + $this->riskFreeRate * $k * exp(-$this->riskFreeRate * $t) * $this->normCdf(-$d2)) / 365;
    }

    private function d1(float $s, float $k, float $t, float $sigma): float
    {
        return (log($s / $k) + ($this->riskFreeRate + 0.5 * $sigma ** 2) * $t) / ($sigma * sqrt(max($t, 1e-6)));
    }

    private function normCdf(float $x): float
    {
        return 0.5 * (1 + $this->erf($x / sqrt(2)));
    }

    private function erf(float $x): float
    {
        $sign = $x < 0 ? -1 : 1;
        $x = abs($x);
        $a1 = 0.254829592;
        $a2 = -0.284496736;
        $a3 = 1.421413741;
        $a4 = -1.453152027;
        $a5 = 1.061405429;
        $p = 0.3275911;
        $t = 1 / (1 + $p * $x);
        $y = 1 - (((((($a5 * $t + $a4) * $t) + $a3) * $t + $a2) * $t + $a1) * $t * exp(-$x * $x));

        return $sign * $y;
    }

    private function normPdf(float $x): float
    {
        return exp(-0.5 * $x * $x) / sqrt(2 * M_PI);
    }
}
