<?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 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')));

        $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');
        }

        $doctorData = null;
        $dataError = null;
        try {
            $doctorData = $this->buildData($repo, $year, $compareYear, $metric, $availableYears, $availableMetrics);
        } 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,
            'doctorData' => $doctorData,
            'dataError' => $dataError,
        ]);
    }

    /**
     * @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 {
        $chartService = new ChartDataService();
        $summary = $repo->summary($year, $compareYear);
        $topDoctors = $repo->topDoctors($year, $metric, 10);
        $compareTopDoctors = $repo->topDoctors($compareYear, $metric, 10);
        $specialties = $repo->specialties($year, $metric, 10);
        $mix = $repo->revenueMix($year);

        $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,
            '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 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);
    }
}
