<?php

namespace App\Services;

use App\Exceptions\DomainException;
use App\Models\Patient;
use App\Models\User;
use App\Support\PhoneNormalizer;
use Illuminate\Support\Facades\DB;

class PatientService
{
    public function __construct(
        private readonly AccessService $access,
        private readonly AuditService $audit,
    ) {}

    public function create(User $actor, array $data): Patient
    {
        if (! $this->access->canCreatePatient($actor)) {
            throw new DomainException('FORBIDDEN', 'You cannot create patients.', 403);
        }

        return DB::transaction(function () use ($actor, $data) {
            $normalized = PhoneNormalizer::normalize($data['phone']);

            $duplicate = Patient::query()
                ->where('organization_id', $actor->organization_id)
                ->whereNull('archived_at')
                ->where('normalized_phone', $normalized)
                ->first();

            if ($duplicate) {
                if (($data['allow_duplicate'] ?? false) && ! $this->access->isAdmin($actor)) {
                    throw new DomainException('FORBIDDEN', 'Only an administrator may override a possible duplicate patient.', 403);
                }

                if (! ($data['allow_duplicate'] ?? false)) {
                    throw new DomainException(
                        'PATIENT_DUPLICATE_POSSIBLE',
                        'A patient with this phone number already exists.',
                        409,
                        ['phone' => ['A possible duplicate patient was found.']],
                        ['patient_id' => $duplicate->id, 'patient_code' => $duplicate->patient_code]
                    );
                }
            }

            $patient = Patient::create([
                'organization_id' => $actor->organization_id,
                'name' => $data['name'],
                'phone' => $data['phone'],
                'normalized_phone' => $normalized,
                'has_whatsapp' => (bool) $data['has_whatsapp'],
                'whatsapp' => ($data['has_whatsapp'] ?? false)
                    ? PhoneNormalizer::normalize($data['whatsapp'] ?? $data['phone'])
                    : null,
                'email' => $data['email'] ?? null,
                'gender' => $data['gender'] ?? null,
                'date_of_birth' => $data['date_of_birth'] ?? null,
                'city' => $data['city'] ?? null,
                'blood_group' => $data['blood_group'] ?? null,
                'allergies' => $data['allergies'] ?? null,
                'emergency_contact' => $data['emergency_contact'] ?? null,
                'notes' => $data['notes'] ?? null,
                'created_by' => $actor->id,
                'updated_by' => $actor->id,
            ]);

            $patient->update([
                'patient_code' => 'PAT-'.str_pad((string) (10000 + $patient->id), 5, '0', STR_PAD_LEFT),
            ]);

            $this->audit->log('patient.created', $actor, $patient, null, $patient->only([
                'patient_code', 'name', 'phone', 'has_whatsapp', 'whatsapp', 'email', 'city',
            ]));

            return $patient->fresh();
        });
    }

    public function update(User $actor, Patient $patient, array $data): Patient
    {
        if (! $this->access->canEditPatient($actor, $patient)) {
            throw new DomainException('FORBIDDEN', 'You cannot edit this patient.', 403);
        }

        return DB::transaction(function () use ($actor, $patient, $data) {
            $before = $patient->only([
                'name', 'phone', 'has_whatsapp', 'whatsapp', 'email', 'gender',
                'date_of_birth', 'city', 'blood_group', 'allergies', 'emergency_contact', 'notes',
            ]);

            if (array_key_exists('phone', $data)) {
                $data['normalized_phone'] = PhoneNormalizer::normalize($data['phone']);

                if ($data['normalized_phone'] !== $patient->normalized_phone) {
                    $duplicate = Patient::query()
                        ->where('organization_id', $actor->organization_id)
                        ->whereNull('archived_at')
                        ->whereKeyNot($patient->id)
                        ->where('normalized_phone', $data['normalized_phone'])
                        ->first();

                    if ($duplicate) {
                        if (($data['allow_duplicate'] ?? false) && ! $this->access->isAdmin($actor)) {
                            throw new DomainException('FORBIDDEN', 'Only an administrator may override a possible duplicate patient.', 403);
                        }

                        if (! ($data['allow_duplicate'] ?? false)) {
                            throw new DomainException(
                                'PATIENT_DUPLICATE_POSSIBLE',
                                'Another patient with this phone number already exists.',
                                409,
                                ['phone' => ['A possible duplicate patient was found.']],
                                ['patient_id' => $duplicate->id, 'patient_code' => $duplicate->patient_code]
                            );
                        }
                    }
                }
            }

            $effectivePhone = $data['phone'] ?? $patient->phone;
            $effectiveHasWhatsapp = array_key_exists('has_whatsapp', $data)
                ? (bool) $data['has_whatsapp']
                : $patient->has_whatsapp;

            if ($effectiveHasWhatsapp) {
                $data['whatsapp'] = PhoneNormalizer::normalize($data['whatsapp'] ?? $patient->whatsapp ?? $effectivePhone);
            } elseif (array_key_exists('has_whatsapp', $data)) {
                $data['whatsapp'] = null;
            }

            unset($data['allow_duplicate']);

            $patient->fill($data);
            $patient->updated_by = $actor->id;
            $patient->save();

            $this->audit->log('patient.updated', $actor, $patient, $before, $patient->only(array_keys($before)));

            return $patient->fresh();
        });
    }

    public function archive(User $actor, Patient $patient): Patient
    {
        if (! $this->access->isAdmin($actor)) {
            throw new DomainException('FORBIDDEN', 'Only an administrator can archive patients.', 403);
        }

        $before = ['archived_at' => $patient->archived_at];
        $patient->update(['archived_at' => now(), 'updated_by' => $actor->id]);
        $this->audit->log('patient.archived', $actor, $patient, $before, ['archived_at' => $patient->archived_at]);

        return $patient;
    }
}
