<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Repositories\RevenueRepository;
use DateTimeImmutable;
use Throwable;

final class RevenuePerformanceProfiler
{
    /** @var string */
    private $hospitalCode;

    /** @var array<string, mixed> */
    private $hospital;

    public function __construct(string $hospitalCode)
    {
        $this->hospitalCode = strtolower($hospitalCode);
        $hospital = \config('hospitals.' . $this->hospitalCode, []);
        $this->hospital = is_array($hospital) ? $hospital : [];
    }

    /**
     * Read-only live profiler. It never performs INSERT/UPDATE/DELETE/MERGE/DDL.
     *
     * @return array<string, mixed>
     */
    public function profile(int $year, int $compareYear, int $iterations = 1): array
    {
        $iterations = max(1, min(5, $iterations));
        $repo = new RevenueRepository($this->hospitalCode);
        $timings = [];
        $errors = [];

        $jobs = [
            'total_revenue' => static function () use ($repo, $year, $compareYear): array {
                return $repo->monthlyRevenue($year, $compareYear, 'total');
            },
            'cash_revenue' => static function () use ($repo, $year, $compareYear): array {
                return $repo->monthlyRevenue($year, $compareYear, 'cash');
            },
            'insurance_revenue' => static function () use ($repo, $year, $compareYear): array {
                return $repo->monthlyRevenue($year, $compareYear, 'insurance');
            },
            'moh_revenue' => function () use ($repo, $year, $compareYear): array {
                return $repo->monthlyRevenue(
                    $year,
                    $compareYear,
                    'moh',
                    (string)($this->hospital['moh_customer_code'] ?? '')
                );
            },
            'profit_loss' => function () use ($repo, $year, $compareYear): array {
                return $repo->monthlyProfitLoss(
                    $year,
                    $compareYear,
                    (int)($this->hospital['profit_loss_company_id'] ?? 0)
                );
            },
        ];

        foreach ($jobs as $name => $job) {
            $samples = [];
            $rowCount = null;
            for ($i = 1; $i <= $iterations; $i++) {
                $started = hrtime(true);
                try {
                    $rows = $job();
                    $elapsedMs = (hrtime(true) - $started) / 1_000_000;
                    $samples[] = round($elapsedMs, 3);
                    $rowCount = count($rows);
                } catch (Throwable $e) {
                    $elapsedMs = (hrtime(true) - $started) / 1_000_000;
                    $samples[] = round($elapsedMs, 3);
                    $errors[$name] = $this->safeError($e);
                    break;
                }
            }
            $timings[$name] = [
                'samples_ms' => $samples,
                'min_ms' => $samples !== [] ? min($samples) : null,
                'max_ms' => $samples !== [] ? max($samples) : null,
                'avg_ms' => $samples !== [] ? round(array_sum($samples) / count($samples), 3) : null,
                'rows' => $rowCount,
                'status' => isset($errors[$name]) ? 'ERROR' : 'OK',
            ];
        }

        $errorCount = count($errors);
        $profileStatus = $errorCount === 0 ? 'COMPLETE' : ($errorCount === count($jobs) ? 'LIVE_UNAVAILABLE' : 'PARTIAL');

        return [
            'schema_version' => '1.0',
            'status' => $profileStatus,
            'phase' => 'H11.0-P11.1-R45-P0',
            'title' => 'Revenue Performance Baseline and Query Profiling',
            'generated_at' => date(DATE_ATOM),
            'hospital' => strtoupper($this->hospitalCode),
            'year' => $year,
            'compare_year' => $compareYear,
            'iterations' => $iterations,
            'revenue_source_scans_in_current_controller' => 4,
            'profit_loss_queries_in_current_controller' => 1,
            'total_primary_query_calls_in_current_controller' => 5,
            'use_revenue_mv_configured' => (bool)\config('app.use_revenue_mv', false),
            'timings' => $timings,
            'errors' => $errors,
            'oracle_diagnostics' => $this->oracleDiagnostics(),
            'year_comparison_contract' => self::yearComparisonContract($year, $compareYear),
            'write_operations' => 'NONE',
        ];
    }

    /** @return array<string, mixed> */
    public static function yearComparisonContract(int $year, int $compareYear, ?DateTimeImmutable $now = null): array
    {
        $now = $now ?? new DateTimeImmutable('now');
        $currentYear = (int)$now->format('Y');
        $currentMonth = (int)$now->format('n');
        $containsCurrentYear = $year === $currentYear || $compareYear === $currentYear;

        if ($containsCurrentYear) {
            $lastCompleteMonth = max(0, $currentMonth - 1);
            $label = $lastCompleteMonth > 0
                ? sprintf('January through %s', $now->modify('first day of this month')->modify('-1 month')->format('F'))
                : 'No completed month is available yet in the current year';

            return [
                'mode' => 'CURRENT_YEAR_FAIR_COMPARISON',
                'current_year' => $currentYear,
                'current_month_excluded' => $currentMonth,
                'last_complete_month' => $lastCompleteMonth,
                'comparison_months' => $lastCompleteMonth > 0 ? range(1, $lastCompleteMonth) : [],
                'period_label' => $label,
                'rule_en' => 'When either selected year is the current year, compare only completed months before the current month, using the same months in both years.',
                'rule_ar' => 'عندما تكون إحدى سنتي المقارنة هي السنة الحالية، تتم مقارنة الأشهر المكتملة السابقة للشهر الحالي فقط مع نفس الأشهر في السنة الأخرى، مع استبعاد الشهر الحالي غير المكتمل.',
            ];
        }

        return [
            'mode' => 'FULL_COMPLETED_YEAR_COMPARISON',
            'comparison_months' => range(1, 12),
            'last_complete_month' => 12,
            'period_label' => 'January through December',
            'rule_en' => 'When both selected years are completed past years, compare all twelve months.',
            'rule_ar' => 'عند مقارنة سنتين ماضيتين مكتملتين، تتم المقارنة على جميع أشهر السنة الاثني عشر.',
        ];
    }

    /** @return array<string, mixed> */
    private function oracleDiagnostics(): array
    {
        $diagnostics = [
            'status' => 'AVAILABLE',
            'objects' => [],
            'indexes' => [],
            'mv' => [],
            'errors' => [],
        ];

        $queries = [
            'objects' => [
                "SELECT table_name, num_rows, blocks, last_analyzed FROM user_tables WHERE table_name IN ('T_INP_OUTP_REVENUE_BATCH')",
                [],
            ],
            'indexes' => [
                "SELECT i.index_name, i.table_name, i.uniqueness, i.status, c.column_name, c.column_position\n"
                . "FROM user_indexes i JOIN user_ind_columns c ON c.index_name = i.index_name AND c.table_name = i.table_name\n"
                . "WHERE i.table_name = 'T_INP_OUTP_REVENUE_BATCH' ORDER BY i.index_name, c.column_position",
                [],
            ],
            'mv' => [
                "SELECT mview_name, staleness, compile_state, last_refresh_type, last_refresh_date FROM user_mviews WHERE mview_name = 'MV_REVENUE_MONTHLY'",
                [],
            ],
        ];

        foreach ($queries as $key => [$sql, $params]) {
            try {
                $diagnostics[$key] = Database::fetchAll($this->hospitalCode, $sql, $params);
            } catch (Throwable $e) {
                $diagnostics['errors'][$key] = $this->safeError($e);
                $diagnostics[$key] = [];
            }
        }

        if (count($diagnostics['errors']) === count($queries)) {
            $diagnostics['status'] = 'UNAVAILABLE';
        } elseif ($diagnostics['errors'] !== []) {
            $diagnostics['status'] = 'PARTIAL';
        }

        $diagnostics['mv_config_enabled'] = (bool)\config('app.use_revenue_mv', false);
        $diagnostics['mv_present'] = !empty($diagnostics['mv']);

        return $diagnostics;
    }

    private function safeError(Throwable $e): string
    {
        $message = trim($e->getMessage());
        return $message !== '' ? $message : get_class($e);
    }
}
