<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Request;
use App\Core\View;
use App\Repositories\DoctorsRepository;
use App\Services\ChartDataService;
use App\Services\FairYearComparisonPolicy;
use Throwable;

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

        $hospitalCode = Auth::hospital();
        $hospital = \config('hospitals.' . $hospitalCode, []);
        $currentYear = (int)date('Y');
        $yearWasRequested = Request::get('year') !== null;
        $compareYearWasRequested = Request::get('compare_year') !== null;
        $year = \safe_int(Request::get('year'), $currentYear);
        $compareYear = \safe_int(Request::get('compare_year'), $year - 1);
        $metric = strtoupper(trim((string)Request::get('metric', 'TOT_REV')));
        $requestedSpecialtyIds = $this->normalizeSpecialtyIds(
            Request::get('specialty_ids', [])
        );

        $repo = new DoctorsRepository($hospitalCode);
        $availableYears = [];
        $availableMetrics = [];
        try {
            $availableMetrics = $repo->availableMetrics();
            $availableYears = $repo->availableYears('TOT_REV', 20);
            if (!$yearWasRequested && !empty($availableYears)) {
                $year = (int)$availableYears[0];
            }
            if (!$compareYearWasRequested && !empty($availableYears)) {
                $compareYear = $this->defaultCompareYear($availableYears, $year);
            }
        } catch (Throwable $e) {
            $this->logDoctorsError('Doctors filter discovery warning', $e);
        }

        $allowedMetrics = array_map(static function (array $row): string {
            return (string)($row['key'] ?? '');
        }, $availableMetrics);
        if (!in_array($metric, $allowedMetrics, true)) {
            $metric = in_array('TOT_REV', $allowedMetrics, true) ? 'TOT_REV' : (string)($allowedMetrics[0] ?? 'TOT_REV');
        }

        $comparisonContract = (new FairYearComparisonPolicy())->resolve($year, $compareYear);
        $startMonth = (int)($comparisonContract['start_month'] ?? 1);
        $endMonth = (int)($comparisonContract['end_month'] ?? 12);

        $availableSpecialties = [];
        $selectedSpecialtyIds = [];
        $specialtyDiscoveryError = null;
        try {
            $availableSpecialties = $repo->availableSpecialties(
                $year,
                $compareYear,
                $startMonth,
                $endMonth
            );
            $selectedSpecialtyIds = $this->validatedSpecialtyIds(
                $requestedSpecialtyIds,
                $availableSpecialties
            );
        } catch (Throwable $e) {
            $specialtyDiscoveryError = $e;
            $this->logDoctorsError('Doctors specialty filter discovery error', $e);
        }

        $doctorData = null;
        $dataError = null;
        try {
            $doctorData = $this->buildData(
                $repo,
                $year,
                $compareYear,
                $metric,
                $availableYears,
                $availableMetrics,
                $comparisonContract,
                $availableSpecialties,
                $selectedSpecialtyIds
            );
        } catch (Throwable $e) {
            $dataError = $e->getMessage();
            $this->logDoctorsError('Doctors workspace error', $e);
        }

        View::render('doctors/index', [
            'title' => \t('doctors.page_title'),
            'activePage' => 'doctors',
            'hospital' => $hospital,
            'hospitalCode' => $hospitalCode,
            'year' => $year,
            'compareYear' => $compareYear,
            'metric' => $metric,
            'availableSpecialties' => $availableSpecialties,
            'selectedSpecialtyIds' => $selectedSpecialtyIds,
            'specialtyFilterAvailable' => (
                $specialtyDiscoveryError === null
                && $availableSpecialties !== []
            ),
            'specialtyFilterWarning' => $specialtyDiscoveryError !== null,
            'doctorData' => $doctorData,
            'comparisonContract' => $comparisonContract,
            'dataError' => $dataError,
        ]);
    }

    /**
     * @return array<int, string>
     */
    private function normalizeSpecialtyIds($raw): array
    {
        $values = is_array($raw) ? $raw : [$raw];
        $out = [];

        foreach ($values as $value) {
            if (is_array($value)) {
                continue;
            }
            $value = trim((string)$value);
            if ($value === '' || in_array($value, $out, true)) {
                continue;
            }
            $out[] = $value;
            if (count($out) >= 50) {
                break;
            }
        }

        return $out;
    }

    /**
     * @param array<int, string> $requested
     * @param array<int, array<string, mixed>> $available
     * @return array<int, string>
     */
    private function validatedSpecialtyIds(
        array $requested,
        array $available
    ): array {
        $allowed = [];
        foreach ($available as $row) {
            $id = trim((string)($row['id'] ?? ''));
            if ($id !== '') {
                $allowed[$id] = true;
            }
        }

        $selected = [];
        foreach ($requested as $id) {
            if (isset($allowed[$id])) {
                $selected[] = $id;
            }
        }

        return $selected;
    }

    /**
     * @param array<int, string> $selected
     * @param array<int, array<string, mixed>> $available
     * @return array<int, string>
     */
    private function selectedSpecialtyLabels(
        array $selected,
        array $available
    ): array {
        $lookup = [];
        foreach ($available as $row) {
            $id = trim((string)($row['id'] ?? ''));
            if ($id !== '') {
                $lookup[$id] = trim((string)($row['label'] ?? $id));
            }
        }

        $labels = [];
        foreach ($selected as $id) {
            if (isset($lookup[$id])) {
                $labels[] = $lookup[$id];
            }
        }

        return $labels;
    }

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

    /**
     * @param array<int, int> $availableYears
     * @param array<int, array<string, mixed>> $availableMetrics
     * @return array<string, mixed>
     */
    private function buildData(
        DoctorsRepository $repo,
        int $year,
        int $compareYear,
        string $metric,
        array $availableYears,
        array $availableMetrics,
        array $comparisonContract,
        array $availableSpecialties,
        array $selectedSpecialtyIds
    ): array {
        $chartService = new ChartDataService();
        $startMonth = (int)($comparisonContract['start_month'] ?? 1);
        $endMonth = (int)($comparisonContract['end_month'] ?? 12);
        $hasComparableMonths = $endMonth >= $startMonth && $endMonth > 0;

        $summary = $hasComparableMonths
            ? $repo->summary($year, $compareYear, $startMonth, $endMonth, $selectedSpecialtyIds)
            : [
                'current_op_visits' => 0,
                'compare_op_visits' => 0,
                'current_inpat_visits' => 0,
                'compare_inpat_visits' => 0,
                'current_revenue' => 0,
                'compare_revenue' => 0,
                'current_moh' => 0,
                'compare_moh' => 0,
                'current_drug' => 0,
                'current_net' => 0,
                'active_doctors' => 0,
                'year' => $year,
                'compare_year' => $compareYear,
            ];
        $topDoctors = $hasComparableMonths
            ? $repo->topDoctors($year, $metric, 10, $startMonth, $endMonth, $selectedSpecialtyIds)
            : [];
        $compareTopDoctors = $hasComparableMonths
            ? $repo->topDoctors($compareYear, $metric, 10, $startMonth, $endMonth, $selectedSpecialtyIds)
            : [];
        $specialties = $hasComparableMonths
            ? $repo->specialties($year, $metric, 10, $startMonth, $endMonth, $selectedSpecialtyIds)
            : [];
        $mix = $hasComparableMonths
            ? $repo->revenueMix($year, $startMonth, $endMonth, $selectedSpecialtyIds)
            : [];
        $currentMonthCoverage = $repo->monthCoverage($year);
        $compareMonthCoverage = $repo->monthCoverage($compareYear);

        $cards = [
            'revenue' => $this->comparisonCard(\t('doctors.card_revenue'), (float)($summary['current_revenue'] ?? 0), (float)($summary['compare_revenue'] ?? 0), $chartService, 'money'),
            'opd' => $this->comparisonCard(\t('doctors.card_opd'), (float)($summary['current_op_visits'] ?? 0), (float)($summary['compare_op_visits'] ?? 0), $chartService, 'count'),
            'inpatient' => $this->comparisonCard(\t('doctors.card_inpatient'), (float)($summary['current_inpat_visits'] ?? 0), (float)($summary['compare_inpat_visits'] ?? 0), $chartService, 'count'),
            'active' => [
                'title' => \t('doctors.card_active'),
                'current_total' => (float)($summary['active_doctors'] ?? 0),
                'compare_total' => null,
                'percent' => null,
                'format' => 'count',
            ],
        ];

        $movements = [];
        foreach (['revenue', 'opd', 'inpatient'] as $key) {
            if (($cards[$key]['percent'] ?? null) !== null) {
                $movements[] = [
                    'key' => $key,
                    'title' => (string)$cards[$key]['title'],
                    'percent' => (float)$cards[$key]['percent'],
                ];
            }
        }
        usort($movements, static function (array $a, array $b): int {
            return abs($b['percent']) <=> abs($a['percent']);
        });

        return [
            'summary' => $summary,
            'cards' => $cards,
            'topDoctors' => $topDoctors,
            'compareTopDoctors' => $compareTopDoctors,
            'specialties' => $specialties,
            'mix' => $mix,
            'metric' => $metric,
            'availableYears' => $availableYears,
            'availableMetrics' => $availableMetrics,
            'specialtyFilter' => [
                'available' => $availableSpecialties,
                'selected_ids' => $selectedSpecialtyIds,
                'selected_labels' => $this->selectedSpecialtyLabels(
                    $selectedSpecialtyIds,
                    $availableSpecialties
                ),
                'active' => $selectedSpecialtyIds !== [],
            ],
            'comparisonContract' => $comparisonContract,
            'periodLabels' => [
                'current' => $this->periodLabel($year, $endMonth),
                'compare' => $this->periodLabel($compareYear, $endMonth),
            ],
            'monthCoverage' => [
                'current' => $currentMonthCoverage,
                'compare' => $compareMonthCoverage,
            ],
            'primaryMovement' => $movements[0] ?? null,
            'topDoctor' => $topDoctors[0] ?? null,
            'topSpecialty' => $specialties[0] ?? null,
            'charts' => [
                'doctors' => [
                    'title' => \t('doctors.top_doctors_title'),
                    'payload' => $this->singleSeriesPayload(\t('doctors.top_doctors_title'), $topDoctors, $this->metricLabel($availableMetrics, $metric)),
                ],
                'specialties' => [
                    'title' => \t('doctors.specialty_title'),
                    'payload' => $this->singleSeriesPayload(\t('doctors.specialty_title'), $specialties, $this->metricLabel($availableMetrics, $metric)),
                ],
            ],
        ];
    }

    /** @return array<string, mixed> */
    private function comparisonCard(string $title, float $current, float $compare, ChartDataService $chartService, string $format): array
    {
        return [
            'title' => $title,
            'current_total' => $current,
            'compare_total' => $compare,
            'percent' => $chartService->percentChange($current, $compare),
            'format' => $format,
        ];
    }

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

    /**
     * @param array<int, array<string, mixed>> $availableMetrics
     */
    private function metricLabel(array $availableMetrics, string $metric): string
    {
        foreach ($availableMetrics as $row) {
            if ((string)($row['key'] ?? '') === $metric) {
                return \t((string)($row['label_key'] ?? 'doctors.metric_revenue'));
            }
        }
        return \t('doctors.metric_revenue');
    }

    private function periodLabel(int $year, int $endMonth): string
    {
        if ($endMonth <= 0) {
            return \current_lang() === 'ar'
                ? 'لا توجد أشهر مكتملة في ' . $year
                : 'No completed months in ' . $year;
        }

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

        return ($months[1] ?? 'Jan') . '–' . ($months[$endMonth] ?? (string)$endMonth) . ' ' . $year;
    }

    private function logDoctorsError(string $label, Throwable $e): void
    {
        $line = '[' . date('Y-m-d H:i:s') . '] ' . $label . ': ' . $e->getMessage() . PHP_EOL;
        @file_put_contents(\base_path('storage/logs/app.log'), $line, FILE_APPEND);
    }
}
