<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Request;
use App\Core\View;
use App\Repositories\BedsRepository;
use App\Services\FairYearComparisonPolicy;
use DateTimeImmutable;
use Throwable;

final class BedsController
{
    public function index(): void
    {
        Auth::requireLogin();

        $hospitalCode = Auth::hospital();
        $hospital = \config('hospitals.' . $hospitalCode, []);
        $currentYear = (int)date('Y');

        $mode = strtolower(trim((string)Request::get('mode', 'year')));
        if (!in_array($mode, ['year', 'range'], true)) {
            $mode = 'year';
        }

        $year = \safe_int(Request::get('year'), $currentYear);
        $compareYear = \safe_int(Request::get('compare_year'), $year - 1);
        $fromDate = trim((string)Request::get('from_date', sprintf('%04d-01-01', $year)));
        $toDate = trim((string)Request::get('to_date', date('Y-m-d')));
        $unitId = \safe_int(Request::get('unit_id'), 0);
        $filterError = '';

        if ($mode === 'range') {
            try {
                $from = new DateTimeImmutable($fromDate);
                $to = new DateTimeImmutable($toDate);
                if ($from > $to || $from->format('Y') !== $to->format('Y')) {
                    throw new \RuntimeException('Invalid range');
                }
                $year = (int)$from->format('Y');
            } catch (Throwable $e) {
                $filterError = \current_lang() === 'ar'
                    ? 'يجب أن تكون فترة التاريخ صحيحة وداخل سنة واحدة.'
                    : 'The date range must be valid and remain within one year.';
                $mode = 'year';
                $year = $currentYear;
            }
        }

        $data = null;
        $dataError = null;
        $comparisonContract = [];
        $periodLabels = ['current' => (string)$year, 'compare' => (string)$compareYear];

        try {
            $repo = new BedsRepository($hospitalCode);
            $years = $repo->availableYears(20);

            foreach ([$currentYear, $year, $compareYear] as $requiredYear) {
                if ($requiredYear > 0 && !in_array($requiredYear, $years, true)) {
                    $years[] = $requiredYear;
                }
            }
            $years = array_values(array_unique(array_map('intval', $years)));
            rsort($years, SORT_NUMERIC);

            if (Request::get('year') === null && $mode === 'year') {
                $year = in_array($currentYear, $years, true)
                    ? $currentYear
                    : (int)($years[0] ?? $currentYear);
            }

            if (Request::get('compare_year') === null || $compareYear === $year) {
                $compareYear = $this->defaultCompareYear($years, $year);
            }

            if ($compareYear === $year) {
                $filterError = \current_lang() === 'ar'
                    ? 'يجب اختيار سنتين مختلفتين للمقارنة.'
                    : 'Choose two different years for comparison.';
                $compareYear = $year - 1;
                if (!in_array($compareYear, $years, true)) {
                    $years[] = $compareYear;
                    rsort($years, SORT_NUMERIC);
                }
            }

            $periods = $this->comparisonPeriods(
                $mode,
                $year,
                $compareYear,
                $fromDate,
                $toDate
            );
            $comparisonContract = $periods['contract'];
            $periodLabels = [
                'current' => $this->periodLabel($periods['current_from'], $periods['current_to']),
                'compare' => $this->periodLabel($periods['compare_from'], $periods['compare_to']),
            ];

            // Live snapshot remains a current operational fact and is not backdated.
            $snapshotRows = $repo->legacyCompatibleCurrentOccupancyRows();
            $snapshot = $this->snapshotSummary($snapshotRows);

            // Historical utilization always uses matched periods in both years.
            $currentUnits = $repo->legacyCompatibleUnitEfficiencyRows(
                $year,
                $periods['current_from'],
                $periods['current_to'],
                0
            );
            $compareUnits = $repo->legacyCompatibleUnitEfficiencyRows(
                $compareYear,
                $periods['compare_from'],
                $periods['compare_to'],
                0
            );

            $unitComparison = $this->mergeEfficiencyRows(
                $currentUnits,
                $compareUnits,
                'unit_id',
                'bed_floor'
            );

            $unitOptions = $this->unitOptions($currentUnits, $compareUnits);
            $isAllUnits = $unitId <= 0;

            $currentBeds = $isAllUnits
                ? []
                : $repo->legacyCompatibleBedEfficiencyRows(
                    $year,
                    $unitId,
                    $periods['current_from'],
                    $periods['current_to']
                );
            $compareBeds = $isAllUnits
                ? []
                : $repo->legacyCompatibleBedEfficiencyRows(
                    $compareYear,
                    $unitId,
                    $periods['compare_from'],
                    $periods['compare_to']
                );

            $bedComparison = $isAllUnits
                ? []
                : $this->mergeEfficiencyRows(
                    $currentBeds,
                    $compareBeds,
                    'bed_name',
                    'bed_name'
                );

            $selectedUnit = $isAllUnits ? null : $this->selectedUnit($currentUnits, $unitId);
            $selectedCompareUnit = $isAllUnits ? null : $this->selectedUnit($compareUnits, $unitId);

            $scopeCurrentUnits = $isAllUnits
                ? $currentUnits
                : array_values(array_filter($currentUnits, static function (array $row) use ($unitId): bool {
                    return (int)($row['unit_id'] ?? 0) === $unitId;
                }));
            $scopeCompareUnits = $isAllUnits
                ? $compareUnits
                : array_values(array_filter($compareUnits, static function (array $row) use ($unitId): bool {
                    return (int)($row['unit_id'] ?? 0) === $unitId;
                }));

            $scopeUnitComparison = $isAllUnits
                ? $unitComparison
                : array_values(array_filter($unitComparison, static function (array $row) use ($unitId): bool {
                    return (int)($row['key'] ?? 0) === $unitId;
                }));

            $periodSummary = [
                'current_utilization' => $this->weightedUtilization($scopeCurrentUnits),
                'compare_utilization' => $this->weightedUtilization($scopeCompareUnits),
                'current_occupied_hours' => $this->sumField($scopeCurrentUnits, 'total_hours_occupied'),
                'compare_occupied_hours' => $this->sumField($scopeCompareUnits, 'total_hours_occupied'),
                'current_possible_hours' => $this->sumField($scopeCurrentUnits, 'total_hours_period'),
                'compare_possible_hours' => $this->sumField($scopeCompareUnits, 'total_hours_period'),
            ];
            $periodSummary['movement_pp'] = $this->difference(
                $periodSummary['current_utilization'],
                $periodSummary['compare_utilization']
            );

            $comparisonReady = !empty($scopeCurrentUnits) && !empty($scopeCompareUnits);

            $data = [
                'years' => $years,
                'snapshot_rows' => $snapshotRows,
                'snapshot' => $snapshot,
                'units' => $currentUnits,
                'compare_units' => $compareUnits,
                'unit_options' => $unitOptions,
                'unit_comparison' => $unitComparison,
                'scope_unit_comparison' => $scopeUnitComparison,
                'scope_current_units' => $scopeCurrentUnits,
                'scope_compare_units' => $scopeCompareUnits,
                'is_all_units' => $isAllUnits,
                'beds' => $currentBeds,
                'compare_beds' => $compareBeds,
                'bed_comparison' => $bedComparison,
                'selected_unit_id' => $unitId,
                'selected_unit' => $selectedUnit,
                'selected_compare_unit' => $selectedCompareUnit,
                'period_summary' => $periodSummary,
                'comparison_ready' => $comparisonReady,
                'comparison_contract' => $comparisonContract,
                'period_labels' => $periodLabels,
                'periods' => $periods,
                'unit_payload' => $this->chartPayload(
                    $currentUnits,
                    'bed_floor',
                    'bed_efficiency_percentage',
                    \current_lang() === 'ar' ? 'استخدام الوحدات' : 'Unit utilization',
                    $periodLabels['current']
                ),
                'bed_payload' => $this->chartPayload(
                    $currentBeds,
                    'bed_name',
                    'bed_efficiency_percentage',
                    \current_lang() === 'ar' ? 'استخدام الأسرة' : 'Bed utilization',
                    $periodLabels['current']
                ),
            ];
        } catch (Throwable $e) {
            $dataError = $e->getMessage();
            @file_put_contents(
                \base_path('storage/logs/app.log'),
                '[' . date('c') . '] Beds workspace error: ' . $e->getMessage() . PHP_EOL,
                FILE_APPEND
            );
        }

        View::render('beds/index', [
            'title' => \current_lang() === 'ar' ? 'الأسرة والسعة' : 'Beds & Unit Capacity',
            'activePage' => 'beds',
            'hospital' => $hospital,
            'hospitalCode' => $hospitalCode,
            'mode' => $mode,
            'year' => $year,
            'compareYear' => $compareYear,
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'unitId' => $unitId,
            'filterError' => $filterError,
            'comparisonContract' => $comparisonContract,
            'periodLabels' => $periodLabels,
            'bedsData' => $data,
            'dataError' => $dataError,
        ]);
    }

    /**
     * @return array{
     *   current_from:string,current_to:string,compare_from:string,compare_to:string,
     *   contract:array<string,mixed>
     * }
     */
    private function comparisonPeriods(
        string $mode,
        int $year,
        int $compareYear,
        string $fromDate,
        string $toDate
    ): array {
        if ($mode === 'range') {
            return [
                'current_from' => $fromDate,
                'current_to' => $toDate,
                'compare_from' => BedsRepository::shiftDateToYear($fromDate, $compareYear),
                'compare_to' => BedsRepository::shiftDateToYear($toDate, $compareYear),
                'contract' => [
                    'mode' => 'range',
                    'fair_current_year_comparison' => false,
                    'matched_custom_period' => true,
                    'current_month_excluded' => null,
                    'start_month' => (int)(new DateTimeImmutable($fromDate))->format('n'),
                    'end_month' => (int)(new DateTimeImmutable($toDate))->format('n'),
                ],
            ];
        }

        $contract = (new FairYearComparisonPolicy())->resolve($year, $compareYear);
        $lastCompleteMonth = (int)($contract['last_complete_month'] ?? 12);

        if ($lastCompleteMonth <= 0) {
            return [
                'current_from' => sprintf('%04d-01-01', $year),
                'current_to' => sprintf('%04d-01-01', $year),
                'compare_from' => sprintf('%04d-01-01', $compareYear),
                'compare_to' => sprintf('%04d-01-01', $compareYear),
                'contract' => $contract,
            ];
        }

        if (!empty($contract['fair_current_year_comparison'])) {
            $currentFrom = (string)$contract['from_date'];
            $currentTo = (string)$contract['to_date'];
        } else {
            $currentFrom = sprintf('%04d-01-01', $year);
            $currentTo = sprintf('%04d-12-31', $year);
        }

        return [
            'current_from' => $currentFrom,
            'current_to' => $currentTo,
            'compare_from' => BedsRepository::shiftDateToYear($currentFrom, $compareYear),
            'compare_to' => BedsRepository::shiftDateToYear($currentTo, $compareYear),
            'contract' => $contract + ['mode' => 'year'],
        ];
    }

    /** @param int[] $years */
    private function defaultCompareYear(array $years, int $year): int
    {
        foreach ($years as $candidate) {
            $candidate = (int)$candidate;
            if ($candidate < $year) {
                return $candidate;
            }
        }
        foreach ($years as $candidate) {
            $candidate = (int)$candidate;
            if ($candidate !== $year) {
                return $candidate;
            }
        }
        return $year - 1;
    }

    private function periodLabel(string $fromDate, string $toDate): string
    {
        $from = new DateTimeImmutable($fromDate);
        $to = new DateTimeImmutable($toDate);
        $isAr = \current_lang() === 'ar';

        $enShort = [1=>'Jan',2=>'Feb',3=>'Mar',4=>'Apr',5=>'May',6=>'Jun',7=>'Jul',8=>'Aug',9=>'Sep',10=>'Oct',11=>'Nov',12=>'Dec'];
        $arShort = [1=>'يناير',2=>'فبراير',3=>'مارس',4=>'أبريل',5=>'مايو',6=>'يونيو',7=>'يوليو',8=>'أغسطس',9=>'سبتمبر',10=>'أكتوبر',11=>'نوفمبر',12=>'ديسمبر'];
        $months = $isAr ? $arShort : $enShort;

        if ($from->format('Y') === $to->format('Y')
            && $from->format('m-d') === '01-01'
            && $to->format('j') === $to->format('t')) {
            return ($months[1] ?? 'Jan')
                . '–'
                . ($months[(int)$to->format('n')] ?? $to->format('M'))
                . ' '
                . $to->format('Y');
        }

        if ($from->format('Y') === $to->format('Y')) {
            return $from->format('j') . ' '
                . ($months[(int)$from->format('n')] ?? $from->format('M'))
                . '–'
                . $to->format('j') . ' '
                . ($months[(int)$to->format('n')] ?? $to->format('M'))
                . ' '
                . $to->format('Y');
        }

        return $from->format('Y-m-d') . ' → ' . $to->format('Y-m-d');
    }

    /**
     * @param array<int,array<string,mixed>> $currentRows
     * @param array<int,array<string,mixed>> $compareRows
     * @return array<int,array<string,mixed>>
     */
    private function mergeEfficiencyRows(
        array $currentRows,
        array $compareRows,
        string $key,
        string $labelKey
    ): array {
        $currentMap = [];
        $compareMap = [];

        foreach ($currentRows as $row) {
            $currentMap[(string)($row[$key] ?? '')] = $row;
        }
        foreach ($compareRows as $row) {
            $compareMap[(string)($row[$key] ?? '')] = $row;
        }

        $keys = array_values(array_unique(array_merge(array_keys($currentMap), array_keys($compareMap))));
        $merged = [];

        foreach ($keys as $rowKey) {
            if ($rowKey === '') {
                continue;
            }
            $current = $currentMap[$rowKey] ?? null;
            $compare = $compareMap[$rowKey] ?? null;
            $currentValue = is_array($current)
                ? (float)($current['bed_efficiency_percentage'] ?? 0)
                : null;
            $compareValue = is_array($compare)
                ? (float)($compare['bed_efficiency_percentage'] ?? 0)
                : null;

            $base = is_array($current) ? $current : (is_array($compare) ? $compare : []);
            $merged[] = [
                'key' => $rowKey,
                'label' => (string)($base[$labelKey] ?? $rowKey),
                'current' => $current,
                'compare' => $compare,
                'current_value' => $currentValue,
                'compare_value' => $compareValue,
                'movement_pp' => $this->difference($currentValue, $compareValue),
                'bed_count' => (float)($base['bed_count'] ?? 0),
            ];
        }

        usort($merged, static function (array $a, array $b): int {
            $av = $a['current_value'];
            $bv = $b['current_value'];
            if ($av === null && $bv === null) {
                return strcmp((string)$a['label'], (string)$b['label']);
            }
            if ($av === null) {
                return 1;
            }
            if ($bv === null) {
                return -1;
            }
            return $bv <=> $av;
        });

        return $merged;
    }

    /** @param array<int,array<string,mixed>> $rows */
    private function weightedUtilization(array $rows): ?float
    {
        $occupied = $this->sumField($rows, 'total_hours_occupied');
        $possible = $this->sumField($rows, 'total_hours_period');

        return $possible > 0 ? ($occupied / $possible) * 100 : null;
    }

    /** @param array<int,array<string,mixed>> $rows */
    private function sumField(array $rows, string $field): float
    {
        $total = 0.0;
        foreach ($rows as $row) {
            $total += (float)($row[$field] ?? 0);
        }
        return $total;
    }

    private function difference(?float $current, ?float $compare): ?float
    {
        if ($current === null || $compare === null) {
            return null;
        }
        return $current - $compare;
    }

    /** @param array<int,array{status:string,rate:float}> $rows @return array<string,float> */
    private function snapshotSummary(array $rows): array
    {
        $busy = 0.0;
        $empty = 0.0;

        foreach ($rows as $row) {
            if (strtolower((string)$row['status']) === 'busy') {
                $busy = (float)$row['rate'];
            }
            if (strtolower((string)$row['status']) === 'empty') {
                $empty = (float)$row['rate'];
            }
        }

        $total = max(0.0, $busy + $empty);

        return [
            'busy' => $busy,
            'empty' => $empty,
            'total' => $total,
            'occupancy_rate' => $total > 0 ? ($busy / $total) * 100 : 0.0,
        ];
    }

    /**
     * @param array<int,array<string,mixed>> $currentUnits
     * @param array<int,array<string,mixed>> $compareUnits
     * @return array<int,array<string,mixed>>
     */
    private function unitOptions(array $currentUnits, array $compareUnits): array
    {
        $options = [];

        foreach (array_merge($compareUnits, $currentUnits) as $row) {
            $unitId = (int)($row['unit_id'] ?? 0);
            if ($unitId <= 0) {
                continue;
            }

            $options[$unitId] = [
                'unit_id' => $unitId,
                'bed_floor' => (string)($row['bed_floor'] ?? ('Unit ' . $unitId)),
            ];
        }

        $options = array_values($options);
        usort($options, static function (array $a, array $b): int {
            return strcasecmp((string)$a['bed_floor'], (string)$b['bed_floor']);
        });

        return $options;
    }

    /** @param array<int,array<string,mixed>> $units @return array<string,mixed>|null */
    private function selectedUnit(array $units, int $unitId): ?array
    {
        foreach ($units as $row) {
            if ((int)($row['unit_id'] ?? 0) === $unitId) {
                return $row;
            }
        }
        return null;
    }

    /** @param array<int,array<string,mixed>> $rows @return array<string,mixed> */
    private function chartPayload(
        array $rows,
        string $labelKey,
        string $valueKey,
        string $title,
        string $periodLabel
    ): array {
        return [
            'title' => $title,
            'period_label' => $periodLabel,
            'labels' => array_map(
                static function (array $row) use ($labelKey): string {
                    return (string)($row[$labelKey] ?? '');
                },
                $rows
            ),
            'series' => [[
                'label' => $periodLabel,
                'values' => array_map(
                    static function (array $row) use ($valueKey): float {
                        return (float)($row[$valueKey] ?? 0);
                    },
                    $rows
                ),
            ]],
        ];
    }
}
