<?php

namespace App\Services;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;

class PreTbRecordPayloadService
{
    public function validateAndNormalize(Request $request, bool $enforceDateDependencies = false): array
    {
        $isExistingRecordUpdate = $request->isMethod('PUT')
            || strtoupper((string) $request->input('_method')) === 'PUT'
            || $request->filled('server_id')
            || $request->filled('id');

        $request->merge([
            'height' => $request->filled('height') ? $request->input('height') : null,
            'weight' => $request->filled('weight') ? $request->input('weight') : null,
            'mode_of_entry' => $this->normalizeModeOfEntryInput($request->input('mode_of_entry')),
            'dofscrrening' => $request->input('dofscrrening') ?: $request->input('date_of_screening'),
            'dofnv' => $request->input('dofnv') ?: null,
            'phone' => $request->filled('phone') ? trim((string) $request->input('phone')) : null,
            'clinic_name' => $request->filled('clinic_name') ? trim((string) $request->input('clinic_name')) : session('tb.clinic'),
            'type_of_specimen_geneXpert' => $this->normalizeSpecimenInput($request->input('type_of_specimen_geneXpert')),
            'type_of_specimen_truenat' => $this->normalizeSpecimenInput($request->input('type_of_specimen_truenat')),
            'tb_diagnosis' => $this->normalizeNullableScalarInput($request->input('tb_diagnosis')),
            'treatment_status' => $this->normalizeNullableScalarInput($request->input('treatment_status')),
        ]);

        $symptomDayFields = [
            'fever' => 'fever_days',
            'cough' => 'cough_days',
            'hemoptysis' => 'hemoptysis_days',
            'weight_loss' => 'weight_loss_days',
            'appetite_loss' => 'appetite_loss_days',
            'chest_pain' => 'chest_pain_days',
            'night_sweats' => 'night_sweats_days',
            'neck_glands' => 'neck_glands_days',
            'fatigue' => 'fatigue_days',
        ];

        $symptomInput = $request->input('symptoms', []);
        $symptomReviewedInput = $request->input('symptoms_reviewed', []);
        $symptomDays = [];
        $symptomPresent = [];
        foreach ($symptomDayFields as $slug => $column) {
            $reviewed = (string) data_get($symptomReviewedInput, $slug) === '1';
            $present = !$reviewed && data_get($symptomInput, "{$slug}.present");
            $presentValue = $present ? '1' : '2';
            $days = $present ? data_get($symptomInput, "{$slug}.days") : null;
            $symptomDays[$column] = $days === '' ? null : $days;
            $symptomPresent[$slug . '_present'] = $presentValue;
        }

        $hasSymptoms = collect($symptomPresent)->contains(function ($v) {
            return (string)$v === '1';
        }) || collect($symptomDays)->filter(function ($value) {
            return $value !== null && $value !== '';
        })->isNotEmpty();

        $request->merge($symptomDays + $symptomPresent + [
            'tb_symptoms' => $hasSymptoms ? '1' : '2',
        ]);

        $rules = [
            'cid' => ['required', 'regex:/^[0-9]{10,12}$/'],
            'clinic_name' => ['nullable', 'string', 'max:100'],
            'name' => ['required', 'string', 'max:255'],
            'age' => ['required', 'numeric', 'min:0', 'max:111'],
            'sex' => ['required', 'in:1,2'],
            'type_of_screening' => ['required', 'in:New,Follow-up,Recheck'],
            'height' => [
                'nullable',
                'numeric',
                function ($attribute, $value, $fail) {
                    if ($value === null || $value === '') {
                        return;
                    }
                    if (!is_numeric($value)) {
                        return;
                    }
                    $h = (float)$value;
                    $validMeters = $h >= 0.3 && $h <= 2.5;
                    $validCm = $h >= 30 && $h <= 250;
                    if (!$validMeters && !$validCm) {
                        $fail('Height must be in cm (30-250) or meters (0.3-2.5).');
                    }
                },
            ],
            'weight' => ['nullable', 'numeric', 'min:1', 'max:300'],
            'mode_of_entry' => ['required', 'regex:/^[1-6](,[1-6])*$/'],
            'dofscrrening' => ['required', 'string'],
            'dofnv' => ['nullable', 'string'],
            'phone' => ['nullable', 'string', 'max:50'],
            'tb_symptoms' => ['nullable', 'in:1,2'],
            'fever_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'cough_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'hemoptysis_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'weight_loss_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'appetite_loss_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'chest_pain_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'night_sweats_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'neck_glands_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'fatigue_days' => ['nullable', 'integer', 'min:0', 'max:365'],
            'fever_present' => ['nullable', 'in:1,2'],
            'cough_present' => ['nullable', 'in:1,2'],
            'hemoptysis_present' => ['nullable', 'in:1,2'],
            'weight_loss_present' => ['nullable', 'in:1,2'],
            'appetite_loss_present' => ['nullable', 'in:1,2'],
            'chest_pain_present' => ['nullable', 'in:1,2'],
            'night_sweats_present' => ['nullable', 'in:1,2'],
            'neck_glands_present' => ['nullable', 'in:1,2'],
            'fatigue_present' => ['nullable', 'in:1,2'],
            'symptoms_reviewed' => ['nullable', 'array'],
            'symptoms_reviewed.*' => ['nullable', 'in:1'],
            'smoking' => ['required', 'in:1,2,3,4'],
            'alcohol' => ['required', 'in:1,2,3,4'],
            'malnutrition' => ['required', 'in:1,2'],
            'PWID' => ['required', 'in:1,2,3,4'],
            'PWUD' => ['required', 'in:1,2,3,4'],
            'DM' => ['required', 'in:1,2,3'],
            'dm_tx_status' => ['nullable', 'in:1,2'],
            'hiv_status' => ['required', 'in:1,2,3'],
            'hiv_tx' => ['nullable', 'in:1,2'],
            'his_tb_self' => ['required', 'in:1,2,3'],
            'his_tb_family' => ['required', 'in:1,2,3'],
            'chest_xray' => ['nullable', 'in:1,2'],
            'chest_xray_date' => ['nullable', 'string'],
            'chest_xray_fac' => ['nullable', 'in:1,2'],
            'xray_image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120'],
            'genexpert' => ['nullable', 'in:1,2'],
            'genexpert_date' => ['nullable', 'string'],
            'type_of_specimen_geneXpert' => ['nullable', 'string', 'max:50'],
            'genexpert_res' => ['nullable', 'in:1,2,3,4,5,6'],
            'truenat' => ['nullable', 'in:1,2'],
            'truenat_date' => ['nullable', 'string'],
            'type_of_specimen_truenat' => ['nullable', 'string', 'max:50'],
            'truenat_res' => ['nullable', 'in:1,2,3,4,5'],
            'hiv_det' => ['nullable', 'in:1,2'],
            'hiv_det_date' => ['nullable', 'string'],
            'hiv_det_res' => ['nullable', 'in:1,2'],
            'crp' => ['nullable', 'in:1,2'],
            'crp_date' => ['nullable', 'string'],
            'crp_result' => ['nullable', 'numeric', 'min:0'],
            'md_diagnosis' => ['nullable', 'in:1,2,3,4'],
            'md_diagnosis_oth' => ['nullable', 'string', 'max:255'],
            'cad_score' => ['nullable', 'numeric', 'min:0'],
            'comment' => ['nullable', 'string'],
            'radio_request' => ['nullable', 'in:1,2'],
            'radio_request_date' => ['nullable', 'string'],
            'radiologist_result' => ['nullable', 'in:1,2,3,4'],
            'radiologist_result_oth' => ['nullable', 'string', 'max:255'],
            'sputum_afb' => ['nullable', 'in:1,2'],
            'sputum_afb_date' => ['nullable', 'string'],
            'sputum_afb_res' => ['nullable', 'in:1,2'],
            'antibiotics' => ['nullable', 'in:1,2'],
            'antibiotic_date' => ['nullable', 'string'],
            'drug' => ['nullable', 'string', 'max:120'],
            'tb_diagnosis' => ['nullable', 'in:DSTB,DRTB,No TB,Need recheck assessment'],
            'treatment_status' => ['nullable', 'in:Rx at MAM-NTP,Refer to NTP,LTFU,Expired,Other'],
            'treatment_status_other' => ['nullable', 'string', 'max:255'],
            'remark' => ['nullable', 'string', 'max:500'],
            'md' => ['nullable', 'string', 'max:100'],
            'main_presenting_complaint' => ['nullable', 'string'],
        ];

        if ($isExistingRecordUpdate) {
            foreach (['smoking', 'alcohol', 'malnutrition', 'PWID', 'PWUD', 'DM', 'hiv_status', 'his_tb_self', 'his_tb_family'] as $field) {
                $rules[$field][0] = 'nullable';
            }
        }

        $validator = Validator::make($request->all(), $rules);
        $validator->after(function ($validator) use ($request, $symptomDayFields, $enforceDateDependencies, $isExistingRecordUpdate) {
            $symptomLabels = [
                'fever' => 'Fever',
                'cough' => 'Cough',
                'hemoptysis' => 'Hemoptysis',
                'weight_loss' => 'Weight loss',
                'appetite_loss' => 'Loss of appetite',
                'chest_pain' => 'Chest pain',
                'night_sweats' => 'Night sweats',
                'neck_glands' => 'Neck glands',
                'fatigue' => 'Fatigue',
            ];

            foreach ($symptomDayFields as $slug => $column) {
                $present = (string) $request->input($slug . '_present') === '1';
                $days = $request->input($column);
                $reviewed = (string) data_get($request->input('symptoms_reviewed', []), $slug);
                $label = $symptomLabels[$slug] ?? ucfirst(str_replace('_', ' ', $slug));

                if ($present) {
                    if ($days === null || $days === '') {
                        if (!$isExistingRecordUpdate) {
                            $validator->errors()->add(
                                "symptoms.{$slug}.days",
                                "{$label}: days is required when symptom is checked."
                            );
                        }
                    }
                    continue;
                }

                if ($reviewed !== '1') {
                    $validator->errors()->add(
                        "symptoms_reviewed.{$slug}",
                        "{$label}: tick the confirm checkbox if this symptom is not present."
                    );
                }
            }

            if ($enforceDateDependencies) {
                $dateDependencies = [
                    [
                        'section' => 'Chest X-ray',
                        'toggle' => 'chest_xray',
                        'date' => 'chest_xray_date',
                        'required' => [
                            'chest_xray_fac' => 'Facility',
                            'md_diagnosis' => 'MD diagnosis',
                        ],
                    ],
                    [
                        'section' => 'Radiologist requested',
                        'toggle' => 'radio_request',
                        'date' => 'radio_request_date',
                        'required' => [
                            'radiologist_result' => 'Result',
                        ],
                    ],
                    [
                        'section' => 'Sputum AFB',
                        'toggle' => 'sputum_afb',
                        'date' => 'sputum_afb_date',
                        'required' => [
                            'sputum_afb_res' => 'Result',
                        ],
                    ],
                    [
                        'section' => 'GeneXpert',
                        'toggle' => 'genexpert',
                        'date' => 'genexpert_date',
                        'required' => [
                            'genexpert_res' => 'Result',
                        ],
                    ],
                    [
                        'section' => 'Truenat',
                        'toggle' => 'truenat',
                        'date' => 'truenat_date',
                        'required' => [
                            'truenat_res' => 'Result',
                        ],
                    ],
                    [
                        'section' => 'HIV (determine)',
                        'toggle' => 'hiv_det',
                        'date' => 'hiv_det_date',
                        'required' => [
                            'hiv_det_res' => 'Result',
                        ],
                    ],
                    [
                        'section' => 'Antibiotics',
                        'toggle' => 'antibiotics',
                        'date' => 'antibiotic_date',
                        'required' => [
                            'drug' => 'Drug',
                        ],
                    ],
                ];

                foreach ($dateDependencies as $dependency) {
                    $dateValue = $request->input($dependency['date']);
                    $hasDate = !($dateValue === null || (is_string($dateValue) && trim($dateValue) === ''));
                    if (!$hasDate) {
                        continue;
                    }

                    if ((string) $request->input($dependency['toggle']) !== '1') {
                        $validator->errors()->add(
                            $dependency['toggle'],
                            "{$dependency['section']}: choose Yes before entering date."
                        );
                    }

                    foreach ($dependency['required'] as $field => $label) {
                        $value = $request->input($field);
                        $isEmpty = $value === null || (is_string($value) && trim($value) === '');
                        if ($isEmpty) {
                            $validator->errors()->add(
                                $field,
                                "{$dependency['section']}: {$label} is required when date is entered."
                            );
                        }
                    }
                }
            }

            if (!$isExistingRecordUpdate) {
                $clinicName = trim((string) $request->input('clinic_name', ''));
                $cid = trim((string) $request->input('cid', ''));
                $prefixes = $this->clinicGeneralIdPrefixes();
                if ($clinicName !== '' && $cid !== '' && array_key_exists($clinicName, $prefixes)) {
                    $expectedPrefix = (string) $prefixes[$clinicName];
                    if ($expectedPrefix !== '' && !str_starts_with($cid, $expectedPrefix)) {
                        $validator->errors()->add(
                            'cid',
                            "General ID must start with {$expectedPrefix} for clinic {$clinicName}."
                        );
                    }
                }
            }
        });

        $validated = $validator->validate();

        [$normHeight, $normWeight] = $this->normalizeAnthro(
            $validated['height'] ?? null,
            $validated['weight'] ?? null
        );
        $validated['height'] = $normHeight;
        $validated['weight'] = $normWeight;

        $validated['bmi'] = $this->calculateBmi(
            $validated['weight'] ?? null,
            $validated['height'] ?? null
        );

        $validated['date_of_screening'] = $this->normalizeDate($request->input('dofscrrening'));
        $validated['dofnv'] = $this->normalizeDate($request->input('dofnv'));
        $validated['chest_xray_date'] = $this->normalizeDate($request->input('chest_xray_date'));
        $validated['genexpert_date'] = $this->normalizeDate($request->input('genexpert_date'));
        $validated['truenat_date'] = $this->normalizeDate($request->input('truenat_date'));
        $validated['hiv_det_date'] = $this->normalizeDate($request->input('hiv_det_date'));
        $validated['radio_request_date'] = $this->normalizeDate($request->input('radio_request_date'));
        $validated['sputum_afb_date'] = $this->normalizeDate($request->input('sputum_afb_date'));
        $validated['antibiotic_date'] = $this->normalizeDate($request->input('antibiotic_date'));
        unset(
            $validated['crp'],
            $validated['crp_date'],
            $validated['crp_result'],
            $validated['tb_treat'],
            $validated['tb_treat_date'],
            $validated['tb_treat_regimen'],
            $validated['manage_oth']
        );
        $validated['comment'] = $this->appendCommentExtras(
            $validated['comment'] ?? null,
            ($validated['md_diagnosis'] ?? null) === '3' ? ($request->input('md_diagnosis_oth') ?: null) : null,
            ($validated['radiologist_result'] ?? null) === '4' ? ($request->input('radiologist_result_oth') ?: null) : null,
            $request->has('md_diagnosis_oth') || $request->has('md_diagnosis'),
            $request->has('radiologist_result_oth') || $request->has('radiologist_result')
        );

        if (($validated['treatment_status'] ?? null) !== 'Other') {
            $validated['treatment_status_other'] = null;
        }

        unset($validated['md_diagnosis_oth'], $validated['radiologist_result_oth']);

        return $validated;
    }

    private function appendCommentExtras(
        ?string $comment,
        ?string $mdDiagnosisOther,
        ?string $radiologistOther,
        bool $replaceMdDiagnosisOther,
        bool $replaceRadiologistOther
    ): ?string
    {
        $clean = $replaceMdDiagnosisOther ? $this->removeCommentSegment($comment, 'MD other:') : $comment;
        $clean = $replaceRadiologistOther ? $this->removeCommentSegment($clean, 'Radiologist other:') : $clean;
        $parts = [];

        if ($clean !== null && trim($clean) !== '') {
            $parts[] = trim($clean);
        }

        if ($mdDiagnosisOther !== null && trim($mdDiagnosisOther) !== '') {
            $parts[] = 'MD other: ' . trim($mdDiagnosisOther);
        }

        if ($radiologistOther !== null && trim($radiologistOther) !== '') {
            $parts[] = 'Radiologist other: ' . trim($radiologistOther);
        }

        return empty($parts) ? null : implode(' | ', $parts);
    }

    private function removeCommentSegment(?string $comment, string $prefix): ?string
    {
        if ($comment === null || trim($comment) === '') {
            return null;
        }

        $prefixBase = rtrim($prefix, ':');
        $prefixPattern = preg_quote($prefixBase, '/');
        $prefixPattern = str_replace('\ ', '\s*', $prefixPattern);
        $pattern = '/' . $prefixPattern . '\\s*:?\\s*.*?(?:\\s*\\|\\s*|$)/i';
        $clean = trim((string) preg_replace($pattern, '', trim($comment)));
        $clean = trim((string) preg_replace('/\\s*\\|\\s*/', ' | ', $clean), " |\t\n\r\0\x0B");

        return $clean !== '' ? $clean : null;
    }

    private function normalizeSpecimenInput($value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $values = is_array($value)
            ? $value
            : preg_split('/\s*,\s*/', (string) $value);

        $allowed = ['Sputum', 'Stool'];
        foreach ($values as $item) {
            $item = trim((string) $item);
            if (in_array($item, $allowed, true)) {
                return $item;
            }
        }

        return null;
    }

    private function normalizeNullableScalarInput($value): ?string
    {
        if (is_array($value)) {
            $values = array_values(array_filter($value, function ($item) {
                return $item !== null && trim((string) $item) !== '';
            }));

            if (empty($values)) {
                return null;
            }

            return trim((string) end($values));
        }

        if ($value === null || trim((string) $value) === '') {
            return null;
        }

        return trim((string) $value);
    }

    private function clinicGeneralIdPrefixes(): array
    {
        return (array) config('tb_entry.general_id_prefixes', []);
    }

    private function normalizeModeOfEntryInput($value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $values = is_array($value)
            ? $value
            : preg_split('/\s*,\s*/', (string) $value);

        $allowedOrder = ['1', '2', '3', '4', '5', '6'];
        $aliases = [
            'general' => '1',
            'plhiv' => '2',
            'ncd' => '3',
            'diabetes' => '3',
            '7' => '3',
            'remote (icmv)' => '4',
            'remote icmv' => '4',
            'remote (tb mobile)' => '5',
            'remote tb mobile' => '5',
            'contact tracing' => '6',
        ];
        $selected = [];
        foreach ($values as $item) {
            $item = trim((string) $item);
            $itemKey = strtolower($item);
            $item = $aliases[$itemKey] ?? $item;
            if (in_array($item, $allowedOrder, true) && !in_array($item, $selected, true)) {
                $selected[] = $item;
            }
        }

        if (empty($selected)) {
            return null;
        }

        usort($selected, fn($a, $b) => array_search($a, $allowedOrder, true) <=> array_search($b, $allowedOrder, true));

        return implode(',', $selected);
    }

    private function normalizeDate(?string $raw): ?string
    {
        if (!$raw) {
            return null;
        }

        if (is_numeric($raw)) {
            try {
                return Carbon::instance(ExcelDate::excelToDateTimeObject($raw))->format('Y-m-d');
            } catch (\Throwable $e) {
                // fall through to other parsers
            }
        }

        $candidates = [
            'd-m-y',
            'd-m-Y',
            'Y-m-d',
            'd/m/Y',
            'd/m/y',
            'd-M-y',
            'd-M-Y',
            'd M Y',
        ];

        foreach ($candidates as $format) {
            try {
                return Carbon::createFromFormat($format, $raw)->format('Y-m-d');
            } catch (\Throwable $e) {
                continue;
            }
        }

        try {
            return Carbon::parse($raw)->format('Y-m-d');
        } catch (\Throwable $e) {
            return null;
        }
    }

    private function normalizeAnthro($height, $weight): array
    {
        $h = is_numeric($height) ? (float)$height : null;
        $w = is_numeric($weight) ? (float)$weight : null;

        if ($h && $h > 3) {
            $h = round($h / 100, 3);
        }

        return [$h, $w];
    }

    private function calculateBmi($weight, $height): ?float
    {
        $w = is_numeric($weight) ? (float)$weight : null;
        $h = is_numeric($height) ? (float)$height : null;
        if (!$w || !$h || $h <= 0) {
            return null;
        }

        return round($w / ($h * $h), 2);
    }
}
