<?php

namespace Modules\ClinicCore\Services\Booking;

use Illuminate\Support\Carbon;
use Modules\ClinicCore\Entities\ClcAppointment;
use Modules\ClinicCore\Entities\ClcDoctor;
use Modules\ClinicCore\Entities\ClcPatient;
use Modules\PortalApi\Contracts\BookingDriver;

/**
 * ClinicCore (human clinic) implementation of the public booking contract. Owns
 * all clinic-specific data access (patients, clinic appointments, clinic doctor
 * schedules). Registered into the BookingDriverRegistry by ClinicCoreServiceProvider.
 *
 * Slot computation is a lean port of the vet BookingSlotResolver, against the
 * clc_* tables. Clinic schedules have no location scoping, so slots are by
 * doctor + day only.
 */
class ClcBookingDriver implements BookingDriver
{
    public function vertical(): string
    {
        return 'medical_clinic';
    }

    public function supports(int $businessId): bool
    {
        return class_exists(ClcDoctor::class)
            && ClcDoctor::where('business_id', $businessId)->exists();
    }

    public function subjectLabel(): string
    {
        return 'Patient';
    }

    public function collectsSpecies(): bool
    {
        return false;
    }

    public function doctors(int $businessId): array
    {
        return ClcDoctor::where('business_id', $businessId)
            ->where('status', 'active')->orderBy('name')
            ->get(['id', 'name'])
            ->map(fn ($d) => ['id' => (int) $d->id, 'name' => (string) $d->name])
            ->all();
    }

    public function availability(int $businessId, \DateTimeInterface $date, ?int $doctorId): array
    {
        $d   = Carbon::instance($date instanceof \DateTimeImmutable ? \DateTime::createFromInterface($date) : $date);
        $now = now();

        // SIFA P3-T7 — all slot rules (effective ranges, breaks, exceptions,
        // capacity / overbooking) now live in the shared ScheduleAvailabilityService
        // so this driver and the staff slot endpoint stay a single source of truth.
        // Contract unchanged: we still return {slots:[{start,label,doctor_name,available}]}.
        $engine = app(ScheduleAvailabilityService::class);

        // Which doctors to compute for (business-scoped — the schedules table isn't).
        $doctorQuery = ClcDoctor::where('business_id', $businessId)->where('status', 'active');
        if ($doctorId) {
            $doctorQuery->where('id', $doctorId);
        }
        $doctors = $doctorQuery->get(['id', 'name']);

        $slots = [];
        foreach ($doctors as $doctor) {
            $computed = $engine->slotsForDoctor($businessId, (int) $doctor->id, $d, null);
            foreach ($computed as $slot) {
                $slotStart = Carbon::parse($d->toDateString() . ' ' . $slot['start'] . ':00');
                // Keep the public form's "no past slots" behaviour.
                if ($slotStart->lt($now)) {
                    continue;
                }
                $slots[] = [
                    'start'       => $slotStart->toIso8601String(),
                    'label'       => $slot['start'],
                    'doctor_name' => (string) $doctor->name,
                    'available'   => (bool) $slot['available'],
                ];
            }
        }

        return ['slots' => $this->dedup($slots)];
    }

    public function createPendingBooking(int $businessId, int $contactId, array $data): array
    {
        $name = (string) ($data['subject_name'] ?? '');
        $patient = ClcPatient::where('business_id', $businessId)
            ->where('contact_id', $contactId)
            ->whereRaw('LOWER(patient_name) = ?', [mb_strtolower($name)])
            ->first();
        if (! $patient) {
            $patient = ClcPatient::create([
                'business_id'  => $businessId,
                'contact_id'   => $contactId,
                'patient_name' => $name,
                'status'       => 'active',
            ]);
        }

        try {
            $dt = Carbon::parse($data['date'] . ' ' . $data['time']);
        } catch (\Throwable $e) {
            $dt = Carbon::parse($data['date'])->setTime(10, 0);
        }
        $doctorId = ! empty($data['doctor_id'])
            ? (int) $data['doctor_id']
            : ($patient->preferred_doctor_id ?? null);

        $attrs = [
            'business_id'      => $businessId,
            'contact_id'       => $contactId,
            'patient_id'       => $patient->id,
            'doctor_id'        => $doctorId ?: null,
            'ref_no'           => 'APT-' . strtoupper(substr(uniqid(), -6)),
            'appointment_date' => $dt->toDateString(),
            'appointment_time' => $dt->format('H:i:s'),
            'duration_minutes' => 30,
            'status'           => 'pending',
            'reason'           => $data['reason'] ?? null,
            'notes'            => '[Online booking request via the public portal]',
        ];
        // SIFA P3-T7 — this portal insert lives in ClinicCore, so stamp the
        // booking source. Guarded so it stays safe before the migration runs.
        if (\Illuminate\Support\Facades\Schema::hasColumn('clc_appointments', 'source')) {
            $attrs['source'] = 'online';
        }

        $apt = ClcAppointment::create($attrs);

        return ['ref_no' => $apt->ref_no];
    }

    /* ─── slot de-dup (per label, prefer available) ─────────────── */

    private function dedup(array $slots): array
    {
        $byLabel = [];
        foreach ($slots as $s) {
            if (! isset($byLabel[$s['label']]) || ($s['available'] && ! $byLabel[$s['label']]['available'])) {
                $byLabel[$s['label']] = $s;
            }
        }
        ksort($byLabel);
        return array_values($byLabel);
    }
}
