<?php

namespace App\Services\Care;

use App\Models\Bed;
use App\Models\BedStay;
use App\Models\CareUnit;
use App\Models\Visit;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class VisitPlacementService
{
    /**
     * Place (or re-place) a visit onto a care unit, optionally a bed.
     */
    public function place(
        Visit $visit,
        CareUnit $unit,
        ?Bed $bed,
        string $ownerRef,
        ?string $actorRef = null,
        ?string $notes = null,
    ): BedStay {
        $this->assertCompatible($visit, $unit, $bed);

        return DB::transaction(function () use ($visit, $unit, $bed, $ownerRef, $actorRef, $notes) {
            $hadPlacement = $visit->care_unit_id !== null;
            $this->endCurrentStay($visit, $ownerRef, $actorRef, $hadPlacement ? 'transfer' : 'admit', $notes);

            if ($bed) {
                $this->occupyBed($bed);
            }

            $stay = BedStay::create([
                'owner_ref' => $ownerRef,
                'organization_id' => $visit->organization_id,
                'visit_id' => $visit->id,
                'care_unit_id' => $unit->id,
                'bed_id' => $bed?->id,
                'status' => BedStay::STATUS_ACTIVE,
                'admitted_at' => now(),
                'admitted_by' => $actorRef,
                'reason' => $hadPlacement ? 'transfer' : 'admit',
                'notes' => $notes,
            ]);

            $visit->update([
                'care_unit_id' => $unit->id,
                'bed_id' => $bed?->id,
                'placed_at' => now(),
                'placed_by' => $actorRef,
            ]);

            AuditLogger::record(
                $ownerRef,
                $hadPlacement ? 'visit.transferred' : 'visit.placed',
                $visit->organization_id,
                $actorRef,
                Visit::class,
                $visit->id,
                [
                    'care_unit_id' => $unit->id,
                    'bed_id' => $bed?->id,
                    'bed_stay_id' => $stay->id,
                ],
            );

            return $stay->fresh(['careUnit', 'bed', 'visit']);
        });
    }

    public function transfer(
        Visit $visit,
        CareUnit $unit,
        ?Bed $bed,
        string $ownerRef,
        ?string $actorRef = null,
        ?string $notes = null,
    ): BedStay {
        if ($visit->care_unit_id === null) {
            throw ValidationException::withMessages([
                'care_unit_id' => 'Visit is not currently placed. Use admit/place instead.',
            ]);
        }

        return $this->place($visit, $unit, $bed, $ownerRef, $actorRef, $notes);
    }

    /**
     * Clear unit/bed placement without completing the visit.
     */
    public function dischargePlacement(
        Visit $visit,
        string $ownerRef,
        ?string $actorRef = null,
        ?string $notes = null,
    ): void {
        $visit->refresh();

        if ($visit->care_unit_id === null && $visit->bed_id === null) {
            return;
        }

        DB::transaction(function () use ($visit, $ownerRef, $actorRef, $notes) {
            $this->endCurrentStay($visit, $ownerRef, $actorRef, 'discharge', $notes);

            $visit->update([
                'care_unit_id' => null,
                'bed_id' => null,
                'placed_at' => null,
                'placed_by' => null,
            ]);

            AuditLogger::record(
                $ownerRef,
                'visit.placement_discharged',
                $visit->organization_id,
                $actorRef,
                Visit::class,
                $visit->id,
            );
        });
    }

    protected function endCurrentStay(
        Visit $visit,
        string $ownerRef,
        ?string $actorRef,
        string $reason,
        ?string $notes,
    ): void {
        $active = BedStay::query()
            ->where('visit_id', $visit->id)
            ->active()
            ->lockForUpdate()
            ->get();

        foreach ($active as $stay) {
            $stay->update([
                'status' => BedStay::STATUS_ENDED,
                'discharged_at' => now(),
                'discharged_by' => $actorRef,
                'reason' => $reason,
                'notes' => $notes ?? $stay->notes,
            ]);

            if ($stay->bed_id) {
                $bed = Bed::query()->lockForUpdate()->find($stay->bed_id);
                if ($bed && $bed->status === 'occupied') {
                    $stillOccupied = BedStay::query()
                        ->where('bed_id', $bed->id)
                        ->active()
                        ->where('id', '!=', $stay->id)
                        ->exists();

                    if (! $stillOccupied) {
                        $bed->update(['status' => 'available']);
                    }
                }
            }
        }

        // Also free visit's current bed if stay rows were missing.
        if ($visit->bed_id) {
            $bed = Bed::query()->lockForUpdate()->find($visit->bed_id);
            if ($bed && $bed->status === 'occupied') {
                $stillOccupied = BedStay::query()
                    ->where('bed_id', $bed->id)
                    ->active()
                    ->exists();
                if (! $stillOccupied) {
                    $bed->update(['status' => 'available']);
                }
            }
        }
    }

    protected function occupyBed(Bed $bed): void
    {
        $bed = Bed::query()->lockForUpdate()->findOrFail($bed->id);

        $conflict = BedStay::query()
            ->where('bed_id', $bed->id)
            ->active()
            ->exists();

        if ($conflict || in_array($bed->status, ['occupied', 'maintenance', 'closed'], true)) {
            throw ValidationException::withMessages([
                'bed_id' => 'This bed is not available.',
            ]);
        }

        $bed->update(['status' => 'occupied']);
    }

    protected function assertCompatible(Visit $visit, CareUnit $unit, ?Bed $bed): void
    {
        if ((int) $visit->organization_id !== (int) $unit->organization_id) {
            throw ValidationException::withMessages([
                'care_unit_id' => 'Care unit belongs to a different organization.',
            ]);
        }

        if (! $unit->is_active) {
            throw ValidationException::withMessages([
                'care_unit_id' => 'Care unit is inactive.',
            ]);
        }

        if ($unit->kind === 'float_pool') {
            throw ValidationException::withMessages([
                'care_unit_id' => 'Patients cannot be placed on a float pool. Float pools are for staff only.',
            ]);
        }

        if (in_array($visit->status, [Visit::STATUS_COMPLETED], true)) {
            throw ValidationException::withMessages([
                'visit_id' => 'Cannot place a completed visit.',
            ]);
        }

        if ($bed) {
            if ((int) $bed->care_unit_id !== (int) $unit->id) {
                throw ValidationException::withMessages([
                    'bed_id' => 'Bed does not belong to the selected care unit.',
                ]);
            }

            if (! $unit->supportsBeds()) {
                throw ValidationException::withMessages([
                    'bed_id' => 'This care unit kind does not use beds.',
                ]);
            }

            if (! $bed->is_active) {
                throw ValidationException::withMessages([
                    'bed_id' => 'Bed is inactive.',
                ]);
            }
        } elseif ($unit->supportsBeds() === false) {
            // outpatient / service — bed optional, OK
        }
    }
}
