<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\InpatientDailyRevenueRuleSettingsRepository;

final class InpatientDailyRevenueService
{
    /** @var array<string,mixed> */
    private array $rules;
    private int $nationalPayerId;
    /** @var array<int,int> */
    private array $tieredPayerIds;
    /** @var array<int,int> */
    private array $cashIds;

    /** @param array<string,mixed>|null $rules */
    public function __construct(string $hospitalCode = 'njch', ?array $rules = null)
    {
        $this->rules = $rules ?? (new InpatientDailyRevenueRuleSettingsRepository($hospitalCode))->load();
        $this->nationalPayerId = (int)($this->rules['national_payer']['company_id'] ?? 0);
        $this->tieredPayerIds = [];
        foreach ((array)($this->rules['tiered_payers'] ?? []) as $payer) {
            if (is_array($payer) && (int)($payer['company_id'] ?? 0) > 0) {
                $this->tieredPayerIds[] = (int)$payer['company_id'];
            }
        }
        $this->cashIds = array_values(array_unique(array_map('intval', (array)($this->rules['cash']['company_ids'] ?? [1]))));
    }

    /**
     * @param array<int, array<string, mixed>> $rows
     * @return array{patients:array<int,array<string,mixed>>,summary:array<string,mixed>,units:array<int,array<string,mixed>>,companies:array<int,array<string,mixed>>}
     */
    public function build(array $rows): array
    {
        $patients = [];
        $unitMap = [];
        $companyMap = [];
        $totalRevenue = 0.0;
        $reviewCount = 0;
        $automaticCount = 0;

        foreach ($rows as $row) {
            $patient = $this->calculatePatient($row);
            $patients[] = $patient;

            $daily = (float)$patient['daily_revenue'];
            $totalRevenue += $daily;
            if ($patient['needs_review']) {
                $reviewCount++;
            } else {
                $automaticCount++;
            }

            $unitKey = trim((string)($patient['unit_name'] ?? '')) ?: 'Unassigned';
            if (!isset($unitMap[$unitKey])) {
                $unitMap[$unitKey] = [
                    'unit_name' => $unitKey,
                    'patients' => 0,
                    'beds' => (int)($patient['bed_counts'] ?? 0),
                    'daily_revenue' => 0.0,
                ];
            }
            $unitMap[$unitKey]['patients']++;
            $unitMap[$unitKey]['daily_revenue'] += $daily;
            $unitMap[$unitKey]['beds'] = max((int)$unitMap[$unitKey]['beds'], (int)($patient['bed_counts'] ?? 0));

            $companyId = (int)$patient['financlassid'];
            $companyKey = $companyId . '|' . (string)$patient['financial_class'];
            if (!isset($companyMap[$companyKey])) {
                $companyMap[$companyKey] = [
                    'financlassid' => $companyId,
                    'financial_class' => (string)$patient['financial_class'],
                    'patients' => 0,
                    'daily_revenue' => 0.0,
                ];
            }
            $companyMap[$companyKey]['patients']++;
            $companyMap[$companyKey]['daily_revenue'] += $daily;
        }

        $units = array_values($unitMap);
        usort($units, static fn(array $a, array $b): int => $b['daily_revenue'] <=> $a['daily_revenue']);

        $companies = array_values($companyMap);
        usort($companies, static fn(array $a, array $b): int => $b['daily_revenue'] <=> $a['daily_revenue']);

        $count = count($patients);
        return [
            'patients' => $patients,
            'summary' => [
                'patient_count' => $count,
                'daily_revenue' => round($totalRevenue, 2),
                'average_revenue' => $count > 0 ? round($totalRevenue / $count, 2) : 0.0,
                'automatic_count' => $automaticCount,
                'automatic_pct' => $count > 0 ? round(($automaticCount / $count) * 100, 1) : 0.0,
                'review_count' => $reviewCount,
                'generated_at' => date('Y-m-d H:i:s'),
            ],
            'units' => $units,
            'companies' => $companies,
        ];
    }

    /** @param array<string,mixed> $row @return array<string,mixed> */
    private function calculatePatient(array $row): array
    {
        $companyId = (int)($row['financlassid'] ?? 0);
        $los = max(1, (int)round((float)($row['los'] ?? 1)));
        $totalLos = max(1, (int)round((float)($row['total_los'] ?? $los)));
        $patientShare = (float)($row['patient_share_total'] ?? 0);
        $companyShare = (float)($row['company_share_total'] ?? 0);
        $methodCode = 'insurance_share_per_los';
        $methodLabel = 'Insurance company share ÷ LOS';
        $dailyRevenue = $companyShare / $los;

        if ($companyId === $this->nationalPayerId) {
            $national = (array)$this->rules['national_payer'];
            $unitType = (string)($row['nursing_unit_type'] ?? '');
            $nicuTypes = (array)($national['nicu_types'] ?? ['NICU']);
            $isNicu = $this->isIcuType($unitType, $nicuTypes);
            $nicuDaysUpTo = max(1, (int)($national['nicu_days_up_to'] ?? 14));

            if ($isNicu) {
                $usesEarlyNicuRate = $totalLos <= $nicuDaysUpTo;
                $dailyRevenue = $usesEarlyNicuRate
                    ? (float)($national['nicu_rate_days_1_to_limit'] ?? 0)
                    : (float)($national['nicu_rate_after_limit'] ?? 0);
                $methodCode = $usesEarlyNicuRate ? 'national_nicu_days_1_to_limit' : 'national_nicu_after_limit';
                $methodLabel = $usesEarlyNicuRate
                    ? 'National payer NICU rate — days 1 to configured limit'
                    : 'National payer NICU rate — after configured limit';
            } else {
                $threshold = max(1, (int)($national['short_stay_days_less_than'] ?? 30));
                $isIcu = $this->isIcuType($unitType, (array)($national['icu_types'] ?? []));
                $usesShortStayIcuRate = $totalLos < $threshold && $isIcu;

                $dailyRevenue = $usesShortStayIcuRate
                    ? (float)($national['short_stay_icu_rate'] ?? 0)
                    : (float)($national['standard_rate'] ?? 0);
                $methodCode = $usesShortStayIcuRate ? 'national_short_stay_icu' : 'national_standard';
                $methodLabel = $usesShortStayIcuRate
                    ? 'National payer short-stay ICU rate'
                    : 'National payer standard rate';
            }
        } elseif (in_array($companyId, $this->tieredPayerIds, true)) {
            $schedule = (array)$this->rules['tier_schedule'];
            if ($los <= 3) {
                $dailyRevenue = (float)($schedule['days_1_3'] ?? 0);
                $methodCode = 'tiered_payer_1_3';
                $methodLabel = 'Tiered payer LOS — days 1–3';
            } elseif ($los <= 8) {
                $dailyRevenue = (float)($schedule['days_4_8'] ?? 0);
                $methodCode = 'tiered_payer_4_8';
                $methodLabel = 'Tiered payer LOS — days 4–8';
            } else {
                $dailyRevenue = (float)($schedule['days_9_plus'] ?? 0);
                $methodCode = 'tiered_payer_9_plus';
                $methodLabel = 'Tiered payer LOS — day 9 and above';
            }
        } elseif (in_array($companyId, $this->cashIds, true)) {
            $dailyRevenue = $patientShare / $los;
            $methodCode = 'cash_patient_share_per_los';
            $methodLabel = 'Cash patient share ÷ LOS';
        }

        $needsReview = false;
        $reviewReason = '';
        if (!is_finite($dailyRevenue) || $dailyRevenue < 0) {
            $needsReview = true;
            $reviewReason = 'Calculated daily revenue is negative or invalid.';
        } elseif (!$this->isFixedOrTieredPayer($companyId)
            && abs(in_array($companyId, $this->cashIds, true) ? $patientShare : $companyShare) < 0.00001) {
            $needsReview = true;
            $reviewReason = 'No accumulated share was found for the active visit.';
        }

        return array_merge($row, [
            'los' => $los,
            'total_los' => $totalLos,
            'patient_share_total' => round($patientShare, 2),
            'company_share_total' => round($companyShare, 2),
            'daily_revenue' => round(max(0.0, $dailyRevenue), 2),
            'method_code' => $methodCode,
            'method_label' => $methodLabel,
            'is_cash_payer' => in_array($companyId, $this->cashIds, true),
            'needs_review' => $needsReview,
            'review_reason' => $reviewReason,
        ]);
    }

    /** @param array<int,mixed> $configuredTypes */
    private function isIcuType(string $actualType, array $configuredTypes): bool
    {
        $actual = strtolower(trim($actualType));
        if ($actual === '') {
            return false;
        }
        foreach ($configuredTypes as $configured) {
            if ($actual === strtolower(trim((string)$configured))) {
                return true;
            }
        }
        return false;
    }

    private function isFixedOrTieredPayer(int $companyId): bool
    {
        return $companyId === $this->nationalPayerId || in_array($companyId, $this->tieredPayerIds, true);
    }
}
