<?php

namespace App\Services\Care\Dentistry;

use App\Models\DentalChart;
use App\Models\DentalChartEvent;
use App\Models\DentalTooth;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Visit;
use App\Services\Care\AuditLogger;
use Illuminate\Support\Facades\DB;

class DentalChartService
{
    public function chartForPatient(Organization $organization, Patient $patient, string $ownerRef): DentalChart
    {
        $chart = DentalChart::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->where('patient_id', $patient->id)
            ->with('teeth')
            ->first();

        if ($chart) {
            $this->ensureTeethForDentition($chart);

            return $chart->fresh('teeth');
        }

        return DB::transaction(function () use ($organization, $patient, $ownerRef) {
            $chart = DentalChart::create([
                'owner_ref' => $ownerRef,
                'organization_id' => $organization->id,
                'patient_id' => $patient->id,
                'notation' => 'fdi',
                'dentition' => DentalCatalog::DENTITION_ADULT,
                'charted_at' => now(),
                'charted_by' => $ownerRef,
            ]);

            $this->seedTeeth($chart, DentalCatalog::DENTITION_ADULT);

            return $chart->fresh('teeth');
        });
    }

    public function setDentition(
        DentalChart $chart,
        string $dentition,
        string $ownerRef,
        ?string $actorRef = null,
    ): DentalChart {
        if (! array_key_exists($dentition, DentalCatalog::dentitions())) {
            throw new \InvalidArgumentException("Invalid dentition [{$dentition}].");
        }

        $chart->update([
            'dentition' => $dentition,
            'charted_at' => now(),
            'charted_by' => $actorRef ?? $ownerRef,
        ]);

        $this->ensureTeethForDentition($chart->fresh());

        AuditLogger::record(
            $ownerRef,
            'dental.chart.dentition_set',
            $chart->organization_id,
            $actorRef,
            DentalChart::class,
            $chart->id,
            ['dentition' => $dentition],
        );

        return $chart->fresh('teeth');
    }

    protected function ensureTeethForDentition(DentalChart $chart): void
    {
        $dentition = $chart->dentition ?: DentalCatalog::DENTITION_ADULT;
        $needed = DentalCatalog::toothCodesForDentition($dentition);
        $existing = $chart->teeth()->pluck('tooth_code')->all();
        $missing = array_diff($needed, $existing);
        foreach ($missing as $code) {
            DentalTooth::create([
                'dental_chart_id' => $chart->id,
                'tooth_code' => $code,
                'status' => DentalTooth::STATUS_PRESENT,
                'surfaces' => [],
                'conditions' => [],
            ]);
        }
    }

    protected function seedTeeth(DentalChart $chart, string $dentition): void
    {
        foreach (DentalCatalog::toothCodesForDentition($dentition) as $code) {
            DentalTooth::create([
                'dental_chart_id' => $chart->id,
                'tooth_code' => $code,
                'status' => DentalTooth::STATUS_PRESENT,
                'surfaces' => [],
                'conditions' => [],
            ]);
        }
    }

    /**
     * @param  array{status?: string, surfaces?: list<string>, conditions?: list<string>, notes?: ?string}  $data
     */
    public function updateTooth(
        DentalChart $chart,
        string $toothCode,
        array $data,
        string $ownerRef,
        ?string $actorRef = null,
        ?Visit $visit = null,
    ): DentalTooth {
        if (! DentalCatalog::isValidToothCode($toothCode)) {
            throw new \InvalidArgumentException("Invalid FDI tooth code [{$toothCode}].");
        }

        $tooth = $chart->teeth()->where('tooth_code', $toothCode)->first();
        if (! $tooth) {
            $tooth = DentalTooth::create([
                'dental_chart_id' => $chart->id,
                'tooth_code' => $toothCode,
                'status' => DentalTooth::STATUS_PRESENT,
                'surfaces' => [],
                'conditions' => [],
            ]);
        }

        $before = [
            'status' => $tooth->status,
            'surfaces' => $tooth->surfaces ?? [],
            'conditions' => $tooth->conditions ?? [],
            'notes' => $tooth->notes,
        ];

        $status = $data['status'] ?? $tooth->status;
        if (! array_key_exists($status, DentalCatalog::toothStatuses())) {
            throw new \InvalidArgumentException("Invalid tooth status [{$status}].");
        }

        $surfaces = array_values(array_intersect(
            array_map('strtoupper', $data['surfaces'] ?? ($tooth->surfaces ?? [])),
            DentalCatalog::surfaces(),
        ));

        $allowedConditions = array_keys(DentalCatalog::conditions());
        $conditions = array_values(array_intersect(
            $data['conditions'] ?? ($tooth->conditions ?? []),
            $allowedConditions,
        ));

        if (in_array($status, [DentalTooth::STATUS_MISSING, DentalTooth::STATUS_EXTRACTED], true)) {
            $surfaces = [];
            if (! in_array('missing', $conditions, true)) {
                $conditions[] = 'missing';
            }
        }

        $tooth->update([
            'status' => $status,
            'surfaces' => $surfaces,
            'conditions' => $conditions,
            'notes' => $data['notes'] ?? $tooth->notes,
        ]);

        $chart->update([
            'charted_at' => now(),
            'charted_by' => $actorRef ?? $ownerRef,
        ]);

        $after = [
            'status' => $tooth->status,
            'surfaces' => $tooth->surfaces ?? [],
            'conditions' => $tooth->conditions ?? [],
            'notes' => $tooth->notes,
        ];

        DentalChartEvent::create([
            'owner_ref' => $ownerRef,
            'organization_id' => $chart->organization_id,
            'dental_chart_id' => $chart->id,
            'patient_id' => $chart->patient_id,
            'visit_id' => $visit?->id,
            'tooth_code' => $toothCode,
            'event_type' => 'tooth_updated',
            'payload' => ['before' => $before, 'after' => $after],
            'recorded_by' => $actorRef ?? $ownerRef,
            'recorded_at' => now(),
        ]);

        AuditLogger::record(
            $ownerRef,
            'dental.chart.tooth_updated',
            $chart->organization_id,
            $actorRef,
            DentalTooth::class,
            $tooth->id,
            ['tooth_code' => $toothCode],
        );

        return $tooth->fresh();
    }

    /**
     * @return array<string, array{status: string, surfaces: list<string>, conditions: list<string>, notes: ?string}>
     */
    public function teethMap(DentalChart $chart): array
    {
        $map = [];
        foreach ($chart->teeth as $tooth) {
            $map[$tooth->tooth_code] = [
                'status' => $tooth->status,
                'surfaces' => $tooth->surfaces ?? [],
                'conditions' => $tooth->conditions ?? [],
                'notes' => $tooth->notes,
            ];
        }

        return $map;
    }
}
