<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\LegacyPatientActivityRepository;
use App\Repositories\VisitsRepository;

final class PatientActivityParityService
{
    /** @var string */
    private $hospitalCode;
    /** @var VisitsRepository */
    private $current;
    /** @var LegacyPatientActivityRepository */
    private $legacy;

    public function __construct(string $hospitalCode)
    {
        $this->hospitalCode = strtolower($hospitalCode);
        $this->current = new VisitsRepository($this->hospitalCode);
        $this->legacy = new LegacyPatientActivityRepository($this->hospitalCode);
    }

    /**
     * Runs four confirmed selected-branch legacy contracts in two modes:
     * year comparison and custom date range. All tests are read-only.
     *
     * @return array<string, mixed>
     */
    public function run(int $year, int $compareYear, string $fromDate, string $toDate): array
    {
        $yearFunctions = $this->runMode($year, $compareYear, null, null);
        $rangeFunctions = $this->runMode($year, $compareYear, $fromDate, $toDate);
        $all = array_merge($yearFunctions, $rangeFunctions);
        $failures = count(array_filter($all, static function (array $row): bool {
            return ($row['status'] ?? 'FAIL') !== 'PASS';
        }));

        return [
            'schema_version' => '1.0',
            'phase' => '10.10',
            'hospital' => strtoupper($this->hospitalCode),
            'year' => $year,
            'compare_year' => $compareYear,
            'range_from' => $fromDate,
            'range_to' => $toDate,
            'write_operations' => 'NONE',
            'source_contract' => [
                'gmov_s' => 'OPD monthly activity; classcode=2; scheduled non-cancelled services; ER excluded by emergency type and clinics 16/297/298',
                'perv_s' => 'ER monthly activity; classcode=2; scheduled non-cancelled services; emergency type=1 or clinic in 16/297/298',
                'padm_s' => 'Admissions monthly activity from RCP_ENTRYEXIT + RCP_VISIT; HOSP_ID=1; normal baby beds only',
                'vfr' => 'New versus follow-up from RES_TYPE; scheduled non-cancelled reservations; HOSP_ID=1',
                'date_range_rule' => 'inclusive current date window compared with the same month/day window shifted to compare year for monthly series',
            ],
            'modes' => [
                'year' => ['functions' => $yearFunctions],
                'date_range' => ['functions' => $rangeFunctions],
            ],
            'functions_checked' => count($all),
            'failures' => $failures,
            'status' => $failures === 0 ? 'PASS' : 'FAIL',
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function runMode(
        int $year,
        int $compareYear,
        ?string $fromDate,
        ?string $toDate
    ): array {
        $results = [];

        $results[] = $this->compareMonthly(
            'gmov_s',
            $this->legacy->monthlyOutpatientVisits($year, $compareYear, $fromDate, $toDate),
            $this->current->monthlyOutpatientVisits($year, $compareYear, $fromDate, $toDate)
        );
        $results[] = $this->compareMonthly(
            'perv_s',
            $this->legacy->monthlyEmergencyVisits($year, $compareYear, $fromDate, $toDate),
            $this->current->monthlyEmergencyVisits($year, $compareYear, $fromDate, $toDate)
        );
        $results[] = $this->compareMonthly(
            'padm_s',
            $this->legacy->monthlyAdmissions($year, $compareYear, $fromDate, $toDate),
            $this->current->monthlyAdmissions($year, $compareYear, $fromDate, $toDate)
        );
        $results[] = $this->compareMix(
            'vfr',
            $this->legacy->followupMix($year, $fromDate, $toDate),
            $this->current->followupMix($year, $fromDate, $toDate)
        );

        return $results;
    }

    /**
     * @param array<int, array<string, mixed>> $legacyRows
     * @param array<int, array<string, mixed>> $currentRows
     * @return array<string, mixed>
     */
    private function compareMonthly(string $legacyFunction, array $legacyRows, array $currentRows): array
    {
        $legacyByMonth = [];
        foreach ($legacyRows as $row) {
            $legacyByMonth[(int)($row['month_id'] ?? 0)] = $row;
        }
        $currentByMonth = [];
        foreach ($currentRows as $row) {
            $currentByMonth[(int)($row['month_id'] ?? 0)] = $row;
        }

        $months = array_unique(array_merge(array_keys($legacyByMonth), array_keys($currentByMonth)));
        sort($months);
        $details = [];
        $legacyCurrentTotal = 0.0;
        $newCurrentTotal = 0.0;
        $legacyCompareTotal = 0.0;
        $newCompareTotal = 0.0;
        $maxDifference = 0.0;
        $ok = count($months) === 12;

        foreach ($months as $month) {
            $legacy = $legacyByMonth[$month] ?? [];
            $current = $currentByMonth[$month] ?? [];
            $legacyCurrent = (float)($legacy['current_value'] ?? 0);
            $newCurrent = (float)($current['current_value'] ?? 0);
            $legacyCompare = (float)($legacy['compare_value'] ?? 0);
            $newCompare = (float)($current['compare_value'] ?? 0);
            $currentDifference = abs($legacyCurrent - $newCurrent);
            $compareDifference = abs($legacyCompare - $newCompare);
            $monthOk = $currentDifference === 0.0 && $compareDifference === 0.0;
            if (!$monthOk) {
                $ok = false;
            }
            $maxDifference = max($maxDifference, $currentDifference, $compareDifference);
            $legacyCurrentTotal += $legacyCurrent;
            $newCurrentTotal += $newCurrent;
            $legacyCompareTotal += $legacyCompare;
            $newCompareTotal += $newCompare;
            $details[] = [
                'month_id' => $month,
                'month_name' => (string)($current['month_name'] ?? $legacy['month_name'] ?? ''),
                'legacy_current' => $legacyCurrent,
                'new_current' => $newCurrent,
                'current_difference' => $currentDifference,
                'legacy_compare' => $legacyCompare,
                'new_compare' => $newCompare,
                'compare_difference' => $compareDifference,
                'status' => $monthOk ? 'PASS' : 'FAIL',
            ];
        }

        return [
            'legacy_function' => $legacyFunction,
            'comparison_type' => 'monthly_two_series',
            'legacy_rows' => count($legacyRows),
            'new_rows' => count($currentRows),
            'legacy_current_total' => $legacyCurrentTotal,
            'new_current_total' => $newCurrentTotal,
            'legacy_compare_total' => $legacyCompareTotal,
            'new_compare_total' => $newCompareTotal,
            'current_total_difference' => abs($legacyCurrentTotal - $newCurrentTotal),
            'compare_total_difference' => abs($legacyCompareTotal - $newCompareTotal),
            'max_month_difference' => $maxDifference,
            'months' => $details,
            'status' => $ok ? 'PASS' : 'FAIL',
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $legacyRows
     * @param array<int, array<string, mixed>> $currentRows
     * @return array<string, mixed>
     */
    private function compareMix(string $legacyFunction, array $legacyRows, array $currentRows): array
    {
        $legacy = $this->mixMap($legacyRows);
        $current = $this->mixMap($currentRows);
        $details = [];
        $maxDifference = 0.0;
        $ok = true;

        foreach (['new', 'followup'] as $category) {
            $legacyValue = (float)($legacy[$category] ?? 0);
            $newValue = (float)($current[$category] ?? 0);
            $difference = abs($legacyValue - $newValue);
            if ($difference !== 0.0) {
                $ok = false;
            }
            $maxDifference = max($maxDifference, $difference);
            $details[] = [
                'category' => $category,
                'legacy_value' => $legacyValue,
                'new_value' => $newValue,
                'difference' => $difference,
                'status' => $difference === 0.0 ? 'PASS' : 'FAIL',
            ];
        }

        return [
            'legacy_function' => $legacyFunction,
            'comparison_type' => 'category_mix',
            'legacy_rows' => count($legacyRows),
            'new_rows' => count($currentRows),
            'legacy_current_total' => array_sum($legacy),
            'new_current_total' => array_sum($current),
            'legacy_compare_total' => 0.0,
            'new_compare_total' => 0.0,
            'current_total_difference' => abs(array_sum($legacy) - array_sum($current)),
            'compare_total_difference' => 0.0,
            'max_month_difference' => $maxDifference,
            'categories' => $details,
            'status' => $ok ? 'PASS' : 'FAIL',
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $rows
     * @return array<string, float>
     */
    private function mixMap(array $rows): array
    {
        $out = ['new' => 0.0, 'followup' => 0.0];
        foreach ($rows as $row) {
            $category = strtolower((string)($row['category'] ?? 'new'));
            $key = $category === 'followup' ? 'followup' : 'new';
            $out[$key] += (float)($row['value'] ?? 0);
        }
        return $out;
    }
}
