<?php

declare(strict_types=1);

namespace App\Services\Recommendations;

use Illuminate\Support\Facades\DB;

final class FeedOfflineEvaluationService
{
    /**
     * @return array<string, mixed>
     */
    public function evaluateAlgo(string $algoVersion, string $from, string $to): array
    {
        $row = DB::table('feed_daily_metrics')
            ->selectRaw('SUM(impressions) as impressions')
            ->selectRaw('SUM(clicks) as clicks')
            ->selectRaw('SUM(saves) as saves')
            ->selectRaw('SUM(dwell_0_5) as dwell_0_5')
            ->selectRaw('SUM(dwell_5_30) as dwell_5_30')
            ->selectRaw('SUM(dwell_30_120) as dwell_30_120')
            ->selectRaw('SUM(dwell_120_plus) as dwell_120_plus')
            ->where('algo_version', $algoVersion)
            ->whereBetween('metric_date', [$from, $to])
            ->first();

        $impressions = (int) ($row->impressions ?? 0);
        $clicks = (int) ($row->clicks ?? 0);
        $saves = (int) ($row->saves ?? 0);

        $dwell05 = (int) ($row->dwell_0_5 ?? 0);
        $dwell530 = (int) ($row->dwell_5_30 ?? 0);
        $dwell30120 = (int) ($row->dwell_30_120 ?? 0);
        $dwell120Plus = (int) ($row->dwell_120_plus ?? 0);

        $ctr = $impressions > 0 ? $clicks / $impressions : 0.0;
        $saveRate = $clicks > 0 ? $saves / $clicks : 0.0;
        $longDwellShare = $clicks > 0 ? ($dwell30120 + $dwell120Plus) / $clicks : 0.0;
        $bounceRate = $clicks > 0 ? $dwell05 / $clicks : 0.0;

        $objectiveWeights = (array) config('discovery.evaluation.objective_weights', []);
        $wCtr = (float) ($objectiveWeights['ctr'] ?? 0.45);
        $wSave = (float) ($objectiveWeights['save_rate'] ?? 0.35);
        $wLong = (float) ($objectiveWeights['long_dwell_share'] ?? 0.25);
        $wBouncePenalty = (float) ($objectiveWeights['bounce_rate_penalty'] ?? 0.15);

        $saveRateInformational = (bool) config('discovery.evaluation.save_rate_informational', true);
        if ($saveRateInformational) {
            $wSave = 0.0;
        }

        $normalizationSum = $wCtr + $wSave + $wLong + $wBouncePenalty;
        if ($normalizationSum > 0.0) {
            $wCtr /= $normalizationSum;
            $wSave /= $normalizationSum;
            $wLong /= $normalizationSum;
            $wBouncePenalty /= $normalizationSum;
        }

        $objectiveScore = ($wCtr * $ctr)
            + ($wSave * $saveRate)
            + ($wLong * $longDwellShare)
            - ($wBouncePenalty * $bounceRate);

        return [
            'algo_version' => $algoVersion,
            'from' => $from,
            'to' => $to,
            'impressions' => $impressions,
            'clicks' => $clicks,
            'saves' => $saves,
            'ctr' => round($ctr, 6),
            'save_rate' => round($saveRate, 6),
            'long_dwell_share' => round($longDwellShare, 6),
            'bounce_rate' => round($bounceRate, 6),
            'dwell_buckets' => [
                '0_5' => $dwell05,
                '5_30' => $dwell530,
                '30_120' => $dwell30120,
                '120_plus' => $dwell120Plus,
            ],
            'objective_score' => round($objectiveScore, 6),
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function evaluateAll(string $from, string $to): array
    {
        $algoVersions = DB::table('feed_daily_metrics')
            ->select('algo_version')
            ->whereBetween('metric_date', [$from, $to])
            ->distinct()
            ->orderBy('algo_version')
            ->pluck('algo_version')
            ->map(static fn (mixed $v): string => (string) $v)
            ->all();

        $out = [];
        foreach ($algoVersions as $algoVersion) {
            $out[] = $this->evaluateAlgo($algoVersion, $from, $to);
        }

        usort($out, static fn (array $a, array $b): int => $b['objective_score'] <=> $a['objective_score']);

        return $out;
    }

    /**
     * @return array<string, mixed>
     */
    public function compareBaselineCandidate(string $baselineAlgoVersion, string $candidateAlgoVersion, string $from, string $to): array
    {
        $baseline = $this->evaluateAlgo($baselineAlgoVersion, $from, $to);
        $candidate = $this->evaluateAlgo($candidateAlgoVersion, $from, $to);

        $deltaObjective = (float) $candidate['objective_score'] - (float) $baseline['objective_score'];
        $objectiveLiftPct = (float) $baseline['objective_score'] !== 0.0
            ? ($deltaObjective / (float) $baseline['objective_score']) * 100.0
            : null;

        return [
            'from' => $from,
            'to' => $to,
            'baseline' => $baseline,
            'candidate' => $candidate,
            'delta' => [
                'objective_score' => round($deltaObjective, 6),
                'objective_lift_pct' => $objectiveLiftPct !== null ? round($objectiveLiftPct, 4) : null,
                'ctr' => round((float) $candidate['ctr'] - (float) $baseline['ctr'], 6),
                'save_rate' => round((float) $candidate['save_rate'] - (float) $baseline['save_rate'], 6),
                'long_dwell_share' => round((float) $candidate['long_dwell_share'] - (float) $baseline['long_dwell_share'], 6),
                'bounce_rate' => round((float) $candidate['bounce_rate'] - (float) $baseline['bounce_rate'], 6),
            ],
        ];
    }
}
