<?php

namespace App\Exports;

use Carbon\Carbon;
use Illuminate\Support\Facades\Crypt;

class Export_age
{
    public static function Export_general($config, $vdate, $DOB, $origin_data, $cutDate = null)
    {
        $historicalReferenceDate = self::resolveDate($vdate);
        $currentReferenceDate = self::resolveCurrentReferenceDate($historicalReferenceDate, $cutDate);

        $pid = (string) (self::getField($config, 'Pid') ?? self::getField($origin_data, 'Pid') ?? '');
        $pidParts = self::parsePid($pid);

        [$storedRegisterAgey, $storedRegisterAgem] = self::normalizeRegisterAgeParts(
            self::normalizeNumber(self::getField($config, 'Agey')),
            self::normalizeNumber(self::getField($config, 'Agem'))
        );

        $registerAgey = $storedRegisterAgey;
        $registerAgem = $storedRegisterAgem ?? 0;

        $dobDate = self::resolveDob($DOB);
        $registerReferenceDate = self::resolveRegistrationReferenceDate($config, $origin_data);

        self::setField($origin_data, 'Age Warning', null);

        /*
         |--------------------------------------------------------------------------
         | Derive register age from DOB only when stored register age is missing
         |--------------------------------------------------------------------------
         */
        if (
            $dobDate !== null &&
            $registerReferenceDate !== null &&
            $storedRegisterAgey === null &&
            $storedRegisterAgem === null
        ) {
            [$derivedRegisterAgey, $derivedRegisterAgem] = self::diffAgeParts($dobDate, $registerReferenceDate);

            if ($registerAgey === null && $derivedRegisterAgey !== null) {
                $registerAgey = $derivedRegisterAgey;
            }

            if ($registerAgem === 0 && $derivedRegisterAgem !== null) {
                $registerAgem = $derivedRegisterAgem;
            }
        }

        /*
         |--------------------------------------------------------------------------
         | Current Age
         |--------------------------------------------------------------------------
         | If DOB gives suspicious age, calculateCurrentAge() will recalculate
         | by using register date + register age.
         */
        [$currentAgey, $currentAgem] = self::calculateCurrentAge(
            $config,
            $origin_data,
            $dobDate,
            $storedRegisterAgey ?? $registerAgey,
            $storedRegisterAgem ?? $registerAgem,
            $pidParts['reg_year'],
            $currentReferenceDate
        );

        /*
         |--------------------------------------------------------------------------
         | Historical Age
         |--------------------------------------------------------------------------
         */
        [$historicalAgey, $historicalAgem] = self::calculateAge(
            $config,
            $origin_data,
            $dobDate,
            $registerAgey,
            $registerAgem,
            $pidParts['reg_year'],
            $historicalReferenceDate
        );

        /*
         |--------------------------------------------------------------------------
         | If historical age is also suspicious, fallback to register age
         |--------------------------------------------------------------------------
         */
        if (
            self::isUntrustedAge($historicalAgey, $historicalAgem) &&
            $registerAgey !== null &&
            $historicalReferenceDate !== null
        ) {
            [$fallbackHistoricalAgey, $fallbackHistoricalAgem] = self::calculateAge(
                $config,
                $origin_data,
                null, // skip DOB
                $registerAgey,
                $registerAgem,
                $pidParts['reg_year'],
                $historicalReferenceDate
            );

            if ($fallbackHistoricalAgey !== null && $fallbackHistoricalAgem !== null) {
                $historicalAgey = $fallbackHistoricalAgey;
                $historicalAgem = $fallbackHistoricalAgem;

                self::appendAgeWarning($origin_data, 'Historical age recalculated from register age because DOB age was suspicious');
            }
        }

        self::setField(
            $origin_data,
            'Clinic Code',
            $pidParts['clinic_code'] ?? self::getField($origin_data, 'Clinic Code') ?? self::getField($config, 'Clinic Code')
        );

        self::setField(
            $origin_data,
            'He Code',
            $pidParts['he_code'] ?? self::getField($origin_data, 'He Code') ?? self::getField($config, 'He Code')
        );

        self::setField($origin_data, 'Reg year', $pidParts['reg_year'] ?? self::getField($origin_data, 'Reg year'));

        self::setField($origin_data, 'Register Agey', $registerAgey);
        self::setField($origin_data, 'Register Agem', $registerAgem);

        self::setField($origin_data, 'Current Agey', $currentAgey);
        self::setField($origin_data, 'Current Agem', $currentAgem);
        self::setField($origin_data, 'Current Age(y)', $currentAgey);
        self::setField($origin_data, 'Current Age(m)', $currentAgem);

        self::setField($origin_data, 'Historical Agey', $historicalAgey);
        self::setField($origin_data, 'Historical Agem', $historicalAgem);
        self::setField($origin_data, 'Historical Age(y)', $historicalAgey);
        self::setField($origin_data, 'Historical Age(m)', $historicalAgem);

        return $origin_data;
    }

    private static function getField($row, string $field)
    {
        if (is_array($row)) {
            return $row[$field] ?? null;
        }

        if ($row instanceof \ArrayAccess) {
            return $row[$field] ?? null;
        }

        return $row->{$field} ?? null;
    }

    private static function setField(&$row, string $field, $value): void
    {
        if (is_array($row) || $row instanceof \ArrayAccess) {
            $row[$field] = $value;
            return;
        }

        $row->{$field} = $value;
    }

    private static function normalizeNumber($value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        return (int) $value;
    }

    private static function normalizeRegisterAgeParts(?int $agey, ?int $agem): array
    {
        if ($agey === null && $agem === null) {
            return [null, null];
        }

        return [$agey ?? 0, $agem ?? 0];
    }

    private static function resolveDob($dob): ?Carbon
    {
        if ($dob === null || $dob === '') {
            return null;
        }

        try {
            return self::resolveDate(Crypt::decryptString($dob));
        } catch (\Throwable $e) {
            return self::resolveDate($dob);
        }
    }

    private static function resolveRegisterDate($config, $originData, ?string $registerYear): ?Carbon
    {
        $registerDate = self::resolveRegistrationReferenceDate($config, $originData);

        if ($registerDate !== null) {
            return $registerDate;
        }

        foreach ([
            'Visit Date',
            'Visit_date',
            'Visit_Date',
            'Counselling_Date'
        ] as $field) {
            $value = self::getField($config, $field) ?? self::getField($originData, $field);

            if ($value) {
                $resolved = self::resolveDate($value);

                if ($resolved !== null) {
                    return $resolved;
                }
            }
        }

        if ($registerYear !== null) {
            return Carbon::create((int) $registerYear, 1, 1)->startOfDay();
        }

        return null;
    }

    private static function resolveRegistrationReferenceDate($config, $originData): ?Carbon
    {
        foreach ([
            'Reg Date',
            'Reg_Date',
            'Reg_date',
            'date_of_screening',
            'dateOfRegistration',
            'Initial_date',
            'Inital_date',
            'IPT_regDate',
            'date_of_admission',
            'date_of_FC_admission'
        ] as $field) {
            $value = self::getField($config, $field) ?? self::getField($originData, $field);

            if (!$value) {
                continue;
            }

            $resolved = self::resolveDate($value);

            if ($resolved !== null) {
                return $resolved;
            }
        }

        return null;
    }

    private static function resolveCurrentReferenceDate(?Carbon $historicalReferenceDate, $cutDate): Carbon
    {
        $ageBasis = self::resolveAgeBasis();
        $resolvedCutDate = self::resolveDate($cutDate ?? self::requestInput('age_cut_date'));

        /*
         | If user explicitly wants visit-date age, use visit date.
         */
        if ($ageBasis === 'visit_date' && $historicalReferenceDate !== null) {
            return $historicalReferenceDate;
        }

        /*
         | Default: use cut date if provided.
         */
        if ($resolvedCutDate !== null) {
            return $resolvedCutDate;
        }

        /*
         | If no cut date, use today.
         */
        return Carbon::now()->startOfDay();
    }

    private static function resolveAgeBasis(): string
    {
        /*
         | Default changed to cut_date/current date.
         | If request has age_basis=visit_date, then it will use visit date.
         */
        $ageBasis = (string) self::requestInput('age_basis', 'cut_date');

        return in_array($ageBasis, ['cut_date', 'visit_date'], true)
            ? $ageBasis
            : 'cut_date';
    }

    private static function calculateAge(
        $config,
        $originData,
        ?Carbon $dobDate,
        ?int $registerAgey,
        int $registerAgem,
        ?string $registerYear,
        ?Carbon $referenceDate
    ): array {
        if ($referenceDate === null) {
            return [null, null];
        }

        /*
         | First priority: calculate from DOB.
         */
        if ($dobDate !== null) {
            return self::diffAgeParts($dobDate, $referenceDate);
        }

        /*
         | Second priority: calculate from register age.
         */
        if ($registerAgey === null) {
            return [null, null];
        }

        $registerDate = self::resolveRegisterDate($config, $originData, $registerYear);

        if ($registerDate !== null) {
            $estimatedDob = $registerDate->copy()
                ->subYears($registerAgey)
                ->subMonths($registerAgem);

            return self::diffAgeParts($estimatedDob, $referenceDate);
        }

        /*
         | Last fallback: use register year only.
         */
        if ($registerYear !== null) {
            return [
                max(0, ($referenceDate->year - (int) $registerYear) + $registerAgey),
                0
            ];
        }

        return [null, null];
    }

    private static function calculateCurrentAge(
        $config,
        $originData,
        ?Carbon $dobDate,
        ?int $registerAgey,
        int $registerAgem,
        ?string $registerYear,
        ?Carbon $referenceDate
    ): array {
        [$currentAgey, $currentAgem] = self::calculateAge(
            $config,
            $originData,
            $dobDate,
            $registerAgey,
            $registerAgem,
            $registerYear,
            $referenceDate
        );

        /*
         |--------------------------------------------------------------------------
         | DOB trust check
         |--------------------------------------------------------------------------
         | If DOB gives suspicious age, ignore DOB and recalculate by using:
         |
         | Register Date + Register Agey + Register Agem
         |
         | Suspicious conditions:
         | - age is null
         | - age is 0 year 0 month
         | - age year is over 105
         | - age month is over 11
         */
        if (
            self::isUntrustedAge($currentAgey, $currentAgem) &&
            $registerAgey !== null
        ) {
            [$fallbackAgey, $fallbackAgem] = self::calculateAge(
                $config,
                $originData,
                null, // important: skip DOB
                $registerAgey,
                $registerAgem,
                $registerYear,
                $referenceDate
            );

            if ($fallbackAgey !== null && $fallbackAgem !== null) {
                self::appendAgeWarning($originData, 'Current age recalculated from register age because DOB age was suspicious');

                return [$fallbackAgey, $fallbackAgem];
            }
        }

        return [$currentAgey, $currentAgem];
    }

    private static function isUntrustedAge(?int $agey, ?int $agem): bool
    {
        if ($agey === null && $agem === null) {
            return true;
        }

        /*
         | You requested:
         | if current age year and current age month are both zero,
         | recalculate from register age.
         */
        if ($agey === 0 && $agem === 0) {
            return true;
        }

        /*
         | If age is over 105, do not trust DOB.
         */
        if ($agey !== null && $agey > 105) {
            return true;
        }

        /*
         | Normal month must be 0 to 11.
         */
        if ($agem !== null && $agem > 11) {
            return true;
        }

        return false;
    }

    private static function appendAgeWarning(&$row, string $warning): void
    {
        $existing = self::getField($row, 'Age Warning');

        if ($existing === null || $existing === '') {
            self::setField($row, 'Age Warning', $warning);
            return;
        }

        self::setField($row, 'Age Warning', $existing . ' | ' . $warning);
    }

    private static function diffAgeParts(?Carbon $fromDate, ?Carbon $toDate): array
    {
        if ($fromDate === null || $toDate === null) {
            return [null, null];
        }

        $fromDate = $fromDate->copy()->startOfDay();
        $toDate = $toDate->copy()->startOfDay();

        if ($toDate->lessThan($fromDate)) {
            return [null, null];
        }

        $age = $fromDate->diff($toDate);

        return [$age->y, $age->m];
    }

    private static function resolveDate($date): ?Carbon
    {
        if ($date === null || $date === '') {
            return null;
        }

        if ($date instanceof Carbon) {
            return $date->copy()->startOfDay();
        }

        $date = trim((string) $date);

        $formats = [
            'Y-m-d',
            'd-m-Y',
            'd/m/Y',
            'Y/m/d',
            'm/d/Y',
            'Y-m-d H:i:s',
            'd-m-Y H:i:s',
            'd/m/Y H:i:s',
        ];

        foreach ($formats as $format) {
            try {
                $parsed = Carbon::createFromFormat($format, $date);

                if ($parsed !== false) {
                    return $parsed->startOfDay();
                }
            } catch (\Throwable $e) {
                // Try next format
            }
        }

        try {
            return Carbon::parse($date)->startOfDay();
        } catch (\Throwable $ignored) {
            return null;
        }
    }

    private static function requestInput(string $key, $default = null)
    {
        if (!function_exists('request') || !app()->bound('request')) {
            return $default;
        }

        return request()->input($key, $default);
    }

    private static function parsePid(string $pid): array
    {
        if ($pid === '') {
            return [
                'clinic_code' => null,
                'he_code' => null,
                'reg_year' => null,
            ];
        }

        if (strlen($pid) === 11) {
            return [
                'clinic_code' => substr($pid, 1, 2),
                'he_code' => substr($pid, 0, 1),
                'reg_year' => '20' . substr($pid, 3, 2),
            ];
        }

        if (strlen($pid) === 12) {
            return [
                'clinic_code' => substr($pid, 2, 2),
                'he_code' => substr($pid, 0, 2),
                'reg_year' => '20' . substr($pid, 4, 2),
            ];
        }

        if (strlen($pid) === 10) {
            return [
                'clinic_code' => substr($pid, 0, 2),
                'he_code' => '',
                'reg_year' => '20' . substr($pid, 2, 2),
            ];
        }

        return [
            'clinic_code' => null,
            'he_code' => null,
            'reg_year' => null,
        ];
    }
}
