<?php

namespace App\Services\Care\Dentistry;

use App\Models\DentalPlanItem;
use App\Models\DentalTreatmentPlan;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Visit;
use App\Services\Care\AuditLogger;
use App\Services\Care\SpecialtyShellService;
use Illuminate\Support\Collection;

class DentalTreatmentPlanService
{
    public function __construct(
        protected SpecialtyShellService $shell,
    ) {}

    public function activePlanForPatient(Organization $organization, Patient $patient, string $ownerRef): ?DentalTreatmentPlan
    {
        return DentalTreatmentPlan::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->where('patient_id', $patient->id)
            ->whereIn('status', [
                DentalTreatmentPlan::STATUS_DRAFT,
                DentalTreatmentPlan::STATUS_ACCEPTED,
                DentalTreatmentPlan::STATUS_IN_PROGRESS,
            ])
            ->with('items')
            ->latest('id')
            ->first();
    }

    /**
     * @return Collection<int, DentalTreatmentPlan>
     */
    public function plansForPatient(Organization $organization, Patient $patient, string $ownerRef, int $limit = 20): Collection
    {
        return DentalTreatmentPlan::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->where('patient_id', $patient->id)
            ->with('items')
            ->latest('id')
            ->limit($limit)
            ->get();
    }

    public function ensurePlan(
        Organization $organization,
        Patient $patient,
        string $ownerRef,
        ?Visit $visit = null,
        ?string $actorRef = null,
    ): DentalTreatmentPlan {
        $plan = $this->activePlanForPatient($organization, $patient, $ownerRef);
        if ($plan) {
            return $plan;
        }

        $plan = DentalTreatmentPlan::create([
            'owner_ref' => $ownerRef,
            'organization_id' => $organization->id,
            'patient_id' => $patient->id,
            'visit_id' => $visit?->id,
            'status' => DentalTreatmentPlan::STATUS_DRAFT,
            'title' => 'Treatment plan',
            'created_by' => $actorRef ?? $ownerRef,
        ]);

        AuditLogger::record($ownerRef, 'dental.plan.created', $organization->id, $actorRef, DentalTreatmentPlan::class, $plan->id);

        return $plan->fresh('items');
    }

    /**
     * @param  array{tooth_code?: ?string, surfaces?: list<string>, procedure_code: string, priority?: int, notes?: ?string}  $data
     */
    public function addItem(
        DentalTreatmentPlan $plan,
        Organization $organization,
        array $data,
        string $ownerRef,
        ?string $actorRef = null,
    ): DentalPlanItem {
        $this->assertPlanMutable($plan);

        $code = (string) ($data['procedure_code'] ?? '');
        $service = collect($this->shell->provisionedServices($organization, 'dentistry'))
            ->first(fn ($s) => ($s['code'] ?? '') === $code)
            ?? DentalCatalog::defaultServices()[$code] ?? null;

        if (! $service) {
            throw new \InvalidArgumentException("Unknown dental procedure [{$code}].");
        }

        $tooth = $data['tooth_code'] ?? null;
        if ($tooth && ! DentalCatalog::isValidToothCode($tooth)) {
            throw new \InvalidArgumentException("Invalid FDI tooth code [{$tooth}].");
        }

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

        $item = DentalPlanItem::create([
            'treatment_plan_id' => $plan->id,
            'tooth_code' => $tooth,
            'surfaces' => $surfaces,
            'procedure_code' => $code,
            'label' => $service['label'],
            'priority' => (int) ($data['priority'] ?? 50),
            'status' => $plan->status === DentalTreatmentPlan::STATUS_DRAFT
                ? DentalPlanItem::STATUS_PROPOSED
                : DentalPlanItem::STATUS_ACCEPTED,
            'estimate_minor' => (int) ($service['amount_minor'] ?? 0),
            'notes' => $data['notes'] ?? null,
        ]);

        $this->recalculateEstimate($plan);

        AuditLogger::record($ownerRef, 'dental.plan.item_added', $organization->id, $actorRef, DentalPlanItem::class, $item->id);

        return $item;
    }

    /**
     * @param  array{tooth_code?: ?string, surfaces?: list<string>, procedure_code?: string, priority?: int, notes?: ?string}  $data
     */
    public function updateItem(
        DentalPlanItem $item,
        Organization $organization,
        array $data,
        string $ownerRef,
        ?string $actorRef = null,
    ): DentalPlanItem {
        if ($item->status === DentalPlanItem::STATUS_DONE) {
            throw new \InvalidArgumentException('Completed plan items cannot be edited.');
        }
        if ($item->status === DentalPlanItem::STATUS_CANCELLED) {
            throw new \InvalidArgumentException('Cancelled plan items cannot be edited.');
        }

        $plan = $item->plan;
        $this->assertPlanMutable($plan);

        $updates = [];
        if (array_key_exists('procedure_code', $data) && $data['procedure_code']) {
            $code = (string) $data['procedure_code'];
            $service = collect($this->shell->provisionedServices($organization, 'dentistry'))
                ->first(fn ($s) => ($s['code'] ?? '') === $code)
                ?? DentalCatalog::defaultServices()[$code] ?? null;
            if (! $service) {
                throw new \InvalidArgumentException("Unknown dental procedure [{$code}].");
            }
            $updates['procedure_code'] = $code;
            $updates['label'] = $service['label'];
            $updates['estimate_minor'] = (int) ($service['amount_minor'] ?? 0);
        }

        if (array_key_exists('tooth_code', $data)) {
            $tooth = $data['tooth_code'];
            if ($tooth && ! DentalCatalog::isValidToothCode($tooth)) {
                throw new \InvalidArgumentException("Invalid FDI tooth code [{$tooth}].");
            }
            $updates['tooth_code'] = $tooth ?: null;
        }

        if (array_key_exists('surfaces', $data)) {
            $updates['surfaces'] = array_values(array_intersect(
                array_map('strtoupper', $data['surfaces'] ?? []),
                DentalCatalog::surfaces(),
            ));
        }

        if (array_key_exists('priority', $data) && $data['priority'] !== null) {
            $updates['priority'] = (int) $data['priority'];
        }

        if (array_key_exists('notes', $data)) {
            $updates['notes'] = $data['notes'];
        }

        $item->update($updates);
        $this->recalculateEstimate($plan);

        AuditLogger::record($ownerRef, 'dental.plan.item_updated', $organization->id, $actorRef, DentalPlanItem::class, $item->id);

        return $item->fresh();
    }

    public function cancelItem(
        DentalPlanItem $item,
        string $ownerRef,
        ?string $actorRef = null,
    ): DentalPlanItem {
        if ($item->status === DentalPlanItem::STATUS_DONE) {
            throw new \InvalidArgumentException('Completed plan items cannot be cancelled.');
        }

        $item->update(['status' => DentalPlanItem::STATUS_CANCELLED]);
        $plan = $item->plan;
        if ($plan) {
            $this->recalculateEstimate($plan);
            if ($plan->items()->whereNotIn('status', [DentalPlanItem::STATUS_DONE, DentalPlanItem::STATUS_CANCELLED])->doesntExist()
                && $plan->items()->where('status', DentalPlanItem::STATUS_DONE)->exists()) {
                $plan->update([
                    'status' => DentalTreatmentPlan::STATUS_COMPLETED,
                    'completed_at' => now(),
                ]);
            }
        }

        AuditLogger::record($ownerRef, 'dental.plan.item_cancelled', $plan?->organization_id, $actorRef, DentalPlanItem::class, $item->id);

        return $item->fresh();
    }

    public function accept(DentalTreatmentPlan $plan, string $ownerRef, ?string $actorRef = null): DentalTreatmentPlan
    {
        if ($plan->status !== DentalTreatmentPlan::STATUS_DRAFT) {
            throw new \InvalidArgumentException('Only draft plans can be accepted.');
        }

        $plan->update([
            'status' => DentalTreatmentPlan::STATUS_ACCEPTED,
            'accepted_at' => now(),
        ]);
        $plan->items()
            ->where('status', DentalPlanItem::STATUS_PROPOSED)
            ->update(['status' => DentalPlanItem::STATUS_ACCEPTED]);

        AuditLogger::record($ownerRef, 'dental.plan.accepted', $plan->organization_id, $actorRef, DentalTreatmentPlan::class, $plan->id);

        return $plan->fresh('items');
    }

    public function rejectPlan(
        DentalTreatmentPlan $plan,
        string $ownerRef,
        ?string $reason = null,
        ?string $actorRef = null,
    ): DentalTreatmentPlan {
        if (! in_array($plan->status, [DentalTreatmentPlan::STATUS_DRAFT, DentalTreatmentPlan::STATUS_ACCEPTED], true)) {
            throw new \InvalidArgumentException('Only draft or accepted plans can be rejected.');
        }

        $plan->update([
            'status' => DentalTreatmentPlan::STATUS_REJECTED,
            'rejected_at' => now(),
            'rejection_reason' => $reason,
        ]);
        $plan->items()
            ->whereNotIn('status', [DentalPlanItem::STATUS_DONE, DentalPlanItem::STATUS_CANCELLED])
            ->update(['status' => DentalPlanItem::STATUS_CANCELLED]);

        AuditLogger::record($ownerRef, 'dental.plan.rejected', $plan->organization_id, $actorRef, DentalTreatmentPlan::class, $plan->id);

        return $plan->fresh('items');
    }

    public function cancelPlan(
        DentalTreatmentPlan $plan,
        string $ownerRef,
        ?string $actorRef = null,
    ): DentalTreatmentPlan {
        if ($plan->status === DentalTreatmentPlan::STATUS_COMPLETED) {
            throw new \InvalidArgumentException('Completed plans cannot be cancelled.');
        }

        $plan->items()
            ->whereNotIn('status', [DentalPlanItem::STATUS_DONE, DentalPlanItem::STATUS_CANCELLED])
            ->update(['status' => DentalPlanItem::STATUS_CANCELLED]);

        $plan->update(['status' => DentalTreatmentPlan::STATUS_CANCELLED]);
        $plan->delete();

        AuditLogger::record($ownerRef, 'dental.plan.cancelled', $plan->organization_id, $actorRef, DentalTreatmentPlan::class, $plan->id);

        return $plan;
    }

    public function markItemDone(DentalPlanItem $item): DentalPlanItem
    {
        $item->update(['status' => DentalPlanItem::STATUS_DONE]);
        $plan = $item->plan;
        if ($plan && $plan->items()->whereNotIn('status', [DentalPlanItem::STATUS_DONE, DentalPlanItem::STATUS_CANCELLED])->doesntExist()) {
            $plan->update([
                'status' => DentalTreatmentPlan::STATUS_COMPLETED,
                'completed_at' => now(),
            ]);
        } elseif ($plan && in_array($plan->status, [DentalTreatmentPlan::STATUS_ACCEPTED, DentalTreatmentPlan::STATUS_DRAFT], true)) {
            $plan->update(['status' => DentalTreatmentPlan::STATUS_IN_PROGRESS]);
        }

        return $item->fresh();
    }

    public function recalculateEstimate(DentalTreatmentPlan $plan): void
    {
        $total = (int) $plan->items()
            ->whereNotIn('status', [DentalPlanItem::STATUS_CANCELLED])
            ->sum('estimate_minor');
        $plan->update(['estimate_minor' => $total]);
    }

    protected function assertPlanMutable(?DentalTreatmentPlan $plan): void
    {
        if (! $plan) {
            throw new \InvalidArgumentException('Plan not found.');
        }
        if (in_array($plan->status, [
            DentalTreatmentPlan::STATUS_COMPLETED,
            DentalTreatmentPlan::STATUS_REJECTED,
            DentalTreatmentPlan::STATUS_CANCELLED,
        ], true)) {
            throw new \InvalidArgumentException('This treatment plan is closed.');
        }
    }
}
