<?php

declare(strict_types=1);

namespace App\Repositories;

use App\Core\Database;
use RuntimeException;
use Throwable;

/**
 * Resolves the Oracle HOSP_ID for Daily Inpatient Revenue without guessing.
 *
 * Resolution order:
 * 1) explicit hospitals.<code>.oracle_branch_id when configured;
 * 2) unique hospital_main_data name match for the selected Oracle database;
 * 3) unique intersection between hospital_main_data matches and active inpatient branches;
 * 4) single active inpatient branch in the selected Oracle database.
 *
 * Oracle writes: NONE.
 */
final class InpatientDailyRevenueBranchResolver
{
    private string $hospitalCode;

    /** @var array<string,array<int,string>> */
    private const NAME_ALIASES = [
        'njch' => ['njch', 'new jeddah', 'new jeddah clinic'],
        'jckh' => ['jckh', 'jeddah clinic hospital', 'jeddah clinic'],
        'mnh' => ['mnh', 'madina', 'medina', 'madinah', 'madina national', 'medina national'],
    ];

    public function __construct(string $hospitalCode)
    {
        $this->hospitalCode = strtolower(trim($hospitalCode));
        if ($this->hospitalCode === '') {
            throw new RuntimeException('Daily inpatient revenue branch code is missing.');
        }
    }

    /**
     * @return array{branch_id:int,source:string,hospital_code:string,configured_branch_id:int,name_match_ids:array<int,int>,active_branch_ids:array<int,int>}
     */
    public function resolve(): array
    {
        $configured = (int)\config('hospitals.' . $this->hospitalCode . '.oracle_branch_id', 0);
        if ($configured > 0) {
            return [
                'branch_id' => $configured,
                'source' => 'CONFIGURED_ORACLE_BRANCH_ID',
                'hospital_code' => $this->hospitalCode,
                'configured_branch_id' => $configured,
                'name_match_ids' => [],
                'active_branch_ids' => [],
            ];
        }

        $nameProbeError = '';
        try {
            $nameMatchIds = $this->hospitalNameMatchIds();
        } catch (Throwable $error) {
            $nameMatchIds = [];
            $nameProbeError = get_class($error) . ': ' . $error->getMessage();
        }
        if (count($nameMatchIds) === 1) {
            return [
                'branch_id' => $nameMatchIds[0],
                'source' => 'HOSPITAL_MAIN_DATA_NAME_MATCH',
                'hospital_code' => $this->hospitalCode,
                'configured_branch_id' => 0,
                'name_match_ids' => $nameMatchIds,
                'active_branch_ids' => [],
                'name_probe_error' => $nameProbeError,
            ];
        }

        $activeBranchIds = $this->activeInpatientBranchIds();
        if ($nameMatchIds !== [] && $activeBranchIds !== []) {
            $intersection = array_values(array_intersect($nameMatchIds, $activeBranchIds));
            $intersection = array_values(array_unique(array_map('intval', $intersection)));
            sort($intersection);
            if (count($intersection) === 1) {
                return [
                    'branch_id' => $intersection[0],
                    'source' => 'HOSPITAL_MAIN_DATA_ACTIVE_BRANCH_INTERSECTION',
                    'hospital_code' => $this->hospitalCode,
                    'configured_branch_id' => 0,
                    'name_match_ids' => $nameMatchIds,
                    'active_branch_ids' => $activeBranchIds,
                    'name_probe_error' => $nameProbeError,
                ];
            }
        }

        if (count($activeBranchIds) === 1) {
            return [
                'branch_id' => $activeBranchIds[0],
                'source' => 'SINGLE_ACTIVE_INPATIENT_BRANCH',
                'hospital_code' => $this->hospitalCode,
                'configured_branch_id' => 0,
                'name_match_ids' => $nameMatchIds,
                'active_branch_ids' => $activeBranchIds,
                'name_probe_error' => $nameProbeError,
            ];
        }

        throw new RuntimeException(
            'Unable to resolve a unique Oracle branch id for Daily Inpatient Revenue. '
            . 'hospital=' . strtoupper($this->hospitalCode)
            . '; name_matches=' . implode(',', $nameMatchIds)
            . '; active_branches=' . implode(',', $activeBranchIds)
            . ($nameProbeError !== '' ? '; name_probe_error=' . $nameProbeError : '')
        );
    }

    /** @return array<int,int> */
    private function hospitalNameMatchIds(): array
    {
        $rows = Database::fetchAll(
            $this->hospitalCode,
            "SELECT hosp_id, hosp_en_name, hosp_short_name_en\n"
            . "FROM hospital_main_data\n"
            . "WHERE ROWNUM <= 100"
        );

        $aliases = self::NAME_ALIASES[$this->hospitalCode] ?? [$this->hospitalCode];
        $matches = [];
        foreach ($rows as $row) {
            $id = (int)($row['hosp_id'] ?? 0);
            if ($id <= 0) {
                continue;
            }
            $name = strtolower(trim(
                (string)($row['hosp_en_name'] ?? '') . ' '
                . (string)($row['hosp_short_name_en'] ?? '')
            ));
            if ($name === '') {
                continue;
            }
            foreach ($aliases as $alias) {
                $alias = strtolower(trim($alias));
                if ($alias !== '' && strpos($name, $alias) !== false) {
                    $matches[$id] = true;
                    break;
                }
            }
        }

        $ids = array_map('intval', array_keys($matches));
        sort($ids);
        return $ids;
    }

    /** @return array<int,int> */
    private function activeInpatientBranchIds(): array
    {
        $rows = Database::fetchAll(
            $this->hospitalCode,
            "SELECT bt.hosp_id, COUNT(*) AS active_bed_rows\n"
            . "FROM rcp_bed_trans bt\n"
            . "WHERE TRUNC(bt.startdate) <= TRUNC(SYSDATE)\n"
            . "  AND bt.enddate IS NULL\n"
            . "GROUP BY bt.hosp_id\n"
            . "ORDER BY active_bed_rows DESC, bt.hosp_id"
        );

        $ids = [];
        foreach ($rows as $row) {
            $id = (int)($row['hosp_id'] ?? 0);
            if ($id > 0) {
                $ids[$id] = true;
            }
        }

        $values = array_map('intval', array_keys($ids));
        sort($values);
        return $values;
    }
}
