<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\RevenueRepository;
use Throwable;

final class RevenueConsolidatedProfiler
{
    /** @var string */
    private $hospitalCode;
    /** @var array<string,mixed> */
    private $hospital;

    public function __construct(string $hospitalCode)
    {
        $this->hospitalCode = strtolower($hospitalCode);
        $hospital = \config('hospitals.' . $this->hospitalCode, []);
        $this->hospital = is_array($hospital) ? $hospital : [];
    }

    /** @return array<string,mixed> */
    public function profile(
        int $year,
        int $compareYear,
        int $iterations = 3,
        ?string $fromDate = null,
        ?string $toDate = null
    ): array {
        $iterations = max(1, min(5, $iterations));
        $repo = new RevenueRepository($this->hospitalCode);
        $mohCode = (string)($this->hospital['moh_customer_code'] ?? '');
        $legacySamples = [];
        $bundleSamples = [];
        $errors = [];
        $legacy = [];
        $bundle = [];

        for ($i = 0; $i < $iterations; $i++) {
            $started = hrtime(true);
            try {
                $legacy = [
                    'total' => $repo->monthlyRevenue($year, $compareYear, 'total', null, $fromDate, $toDate),
                    'cash' => $repo->monthlyRevenue($year, $compareYear, 'cash', null, $fromDate, $toDate),
                    'insurance' => $repo->monthlyRevenue($year, $compareYear, 'insurance', null, $fromDate, $toDate),
                    'moh' => $repo->monthlyRevenue($year, $compareYear, 'moh', $mohCode, $fromDate, $toDate),
                ];
                $legacySamples[] = round((hrtime(true) - $started) / 1000000, 3);
            } catch (Throwable $e) {
                $legacySamples[] = round((hrtime(true) - $started) / 1000000, 3);
                $errors['legacy_four_query_path'] = $e->getMessage();
                break;
            }
        }

        for ($i = 0; $i < $iterations; $i++) {
            $started = hrtime(true);
            try {
                $bundle = $repo->monthlyRevenueBundle($year, $compareYear, $mohCode, $fromDate, $toDate);
                $bundleSamples[] = round((hrtime(true) - $started) / 1000000, 3);
            } catch (Throwable $e) {
                $bundleSamples[] = round((hrtime(true) - $started) / 1000000, 3);
                $errors['consolidated_one_query_path'] = $e->getMessage();
                break;
            }
        }

        $parity = $errors === [] ? $this->compareBundles($legacy, $bundle) : [
            'status' => 'UNAVAILABLE',
            'checks' => 0,
            'passed' => 0,
            'failed' => 0,
            'differences' => [],
        ];

        $legacyAvg = $this->average($legacySamples);
        $bundleAvg = $this->average($bundleSamples);
        $improvement = ($legacyAvg !== null && $legacyAvg > 0 && $bundleAvg !== null)
            ? round((($legacyAvg - $bundleAvg) / $legacyAvg) * 100, 2)
            : null;

        return [
            'schema_version' => '1.0',
            'phase' => 'H11.0-P11.1-R45-P2',
            'release' => '11.0.11.57',
            'generated_at' => date(DATE_ATOM),
            'hospital' => strtoupper($this->hospitalCode),
            'year' => $year,
            'compare_year' => $compareYear,
            'from_date' => $fromDate,
            'to_date' => $toDate,
            'iterations' => $iterations,
            'legacy_revenue_query_calls_per_page' => 4,
            'consolidated_revenue_query_calls_per_page' => 1,
            'profit_loss_query_calls_per_page' => 1,
            'total_primary_calls_before' => 5,
            'total_primary_calls_after' => 2,
            'legacy_four_query_path' => $this->stats($legacySamples),
            'consolidated_one_query_path' => $this->stats($bundleSamples),
            'average_improvement_percent' => $improvement,
            'numeric_parity' => $parity,
            'errors' => $errors,
            'status' => $errors !== [] ? 'LIVE_UNAVAILABLE' : ($parity['status'] === 'PASS' ? 'PASS' : 'FAIL'),
            'oracle_writes' => 'NONE',
        ];
    }

    /** @param array<string,array<int,array<string,mixed>>> $expected
     *  @param array<string,array<int,array<string,mixed>>> $actual
     *  @return array<string,mixed>
     */
    private function compareBundles(array $expected, array $actual): array
    {
        $differences = [];
        $checks = 0;
        $passed = 0;
        foreach (['total', 'cash', 'insurance', 'moh'] as $type) {
            $actualByMonth = [];
            foreach (($actual[$type] ?? []) as $row) {
                $actualByMonth[(int)($row['code'] ?? 0)] = $row;
            }
            foreach (($expected[$type] ?? []) as $row) {
                $month = (int)($row['code'] ?? 0);
                $other = $actualByMonth[$month] ?? [];
                foreach (['current_value', 'compare_value'] as $column) {
                    $checks++;
                    $expectedValue = (float)($row[$column] ?? 0);
                    $actualValue = (float)($other[$column] ?? 0);
                    $delta = $actualValue - $expectedValue;
                    if (abs($delta) <= 0.01) {
                        $passed++;
                    } else {
                        $differences[] = [
                            'type' => $type,
                            'month' => $month,
                            'column' => $column,
                            'expected' => $expectedValue,
                            'actual' => $actualValue,
                            'delta' => $delta,
                        ];
                    }
                }
            }
        }
        return [
            'status' => $checks > 0 && $passed === $checks ? 'PASS' : 'FAIL',
            'checks' => $checks,
            'passed' => $passed,
            'failed' => $checks - $passed,
            'tolerance' => 0.01,
            'differences' => $differences,
        ];
    }

    /** @param array<int,float> $samples @return array<string,mixed> */
    private function stats(array $samples): array
    {
        return [
            'samples_ms' => $samples,
            'min_ms' => $samples !== [] ? min($samples) : null,
            'avg_ms' => $this->average($samples),
            'max_ms' => $samples !== [] ? max($samples) : null,
        ];
    }

    /** @param array<int,float> $samples */
    private function average(array $samples): ?float
    {
        return $samples !== [] ? round(array_sum($samples) / count($samples), 3) : null;
    }
}
