<?php

namespace Modules\MedSurgery\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;
use Modules\MedSurgery\Entities\MedsxCase;
use Modules\MedSurgery\Entities\MedsxCount;

/**
 * SurgeryService (SIFA P6-T4) — the surgical-case engine.
 *
 * Responsibilities:
 *   - status lifecycle transitions (scheduled → pre_op → in_or → post_op →
 *     completed, plus cancelled / postponed);
 *   - the CONSENT GATE: moving to in_or requires consent_id set AND the time_out
 *     checklist signed, else a 422 (and, when consent is missing, an idempotent
 *     MedicalAlertService `consent_missing` alert that resolves when consent is
 *     set);
 *   - the COUNT DISCREPANCY rule: a case cannot reach `completed` while any count
 *     is in discrepancy and unacknowledged;
 *   - OR board conflict detection (overlapping scheduled times per room).
 *
 * All ClinicCore / MedOrganization integration is fail-soft and business-scoped.
 */
class SurgeryService
{
    /** Allowed source→target status transitions. */
    protected array $transitions = [
        'scheduled'  => ['pre_op', 'in_or', 'cancelled', 'postponed'],
        'pre_op'     => ['in_or', 'cancelled', 'postponed'],
        'in_or'      => ['post_op', 'cancelled'],
        'post_op'    => ['completed'],
        'completed'  => [],
        'cancelled'  => [],
        'postponed'  => ['scheduled', 'pre_op', 'cancelled'],
    ];

    protected ChecklistService $checklists;

    public function __construct()
    {
        $this->checklists = app(ChecklistService::class);
    }

    /**
     * Perform a status transition with all gates enforced.
     *
     * @param  array  $opts  cancellation_reason?, discrepancy_note?
     * @throws ValidationException on an illegal transition or a failed gate (422).
     */
    public function transition(MedsxCase $case, string $target, array $opts = []): MedsxCase
    {
        $this->assertTransition($case, $target);

        if ($target === 'in_or') {
            $this->assertConsentGate($case);
        }

        if ($target === 'completed') {
            $this->assertCountsResolved($case, $opts['discrepancy_note'] ?? null);
        }

        $attrs = ['status' => $target];

        // Stamp actual OR times as the case flows through theatre.
        if ($target === 'in_or' && ! $case->actual_start) {
            $attrs['actual_start'] = Carbon::now();
        }
        if ($target === 'post_op' && ! $case->actual_end) {
            $attrs['actual_end'] = Carbon::now();
        }
        if (in_array($target, ['cancelled', 'postponed'], true) && ! empty($opts['cancellation_reason'])) {
            $attrs['cancellation_reason'] = $opts['cancellation_reason'];
        }

        $case->update($attrs);

        return $case->fresh();
    }

    /**
     * CONSENT GATE (spec §16 mandatory_before_incision).
     * in_or requires: consent_id set AND the time_out checklist signed.
     * When consent is missing, raise an idempotent `consent_missing` alert.
     */
    protected function assertConsentGate(MedsxCase $case): void
    {
        $errors = [];

        if (empty($case->consent_id)) {
            $this->raiseConsentAlert($case);
            $errors['consent'] = __('medsurgery::lang.gate_consent_required');
        }

        $timeOut = $this->checklists->timeOut($case);
        if (! $timeOut || ! $timeOut->isComplete()) {
            $errors['time_out'] = __('medsurgery::lang.gate_time_out_required');
        }

        if (! empty($errors)) {
            throw ValidationException::withMessages($errors);
        }

        // Both satisfied — clear any lingering consent_missing alert.
        $this->resolveConsentAlert($case);
    }

    /**
     * COUNT DISCREPANCY rule (spec §16 sponge_count: cannot close until resolved).
     * Any count in discrepancy blocks `completed` until acknowledged with a note.
     * The note is appended to the case's complications field (the durable record).
     */
    protected function assertCountsResolved(MedsxCase $case, ?string $note): void
    {
        $hasDiscrepancy = $case->counts()->where('discrepancy', true)->exists();

        if ($hasDiscrepancy && empty($note)) {
            throw ValidationException::withMessages([
                'discrepancy_note' => __('medsurgery::lang.gate_count_discrepancy'),
            ]);
        }

        if ($hasDiscrepancy && ! empty($note)) {
            $stamp = __('medsurgery::lang.count_discrepancy_ack', [
                'note' => $note,
                'time' => Carbon::now()->toDateTimeString(),
            ]);
            $newComplications = trim(((string) $case->complications) . "\n" . $stamp);
            $case->update(['complications' => $newComplications]);
        }
    }

    /** Guard: throw a validation error if the transition isn't allowed. */
    protected function assertTransition(MedsxCase $case, string $target): void
    {
        $allowed = $this->transitions[$case->status] ?? [];
        if (! in_array($target, $allowed, true)) {
            throw ValidationException::withMessages([
                'status' => __('medsurgery::lang.invalid_transition', [
                    'from' => $case->status,
                    'to'   => $target,
                ]),
            ]);
        }
    }

    // ── OR board conflict detection ─────────────────────────────────────────

    /**
     * Conflicts for a case's scheduled slot: other cases in the same OR room whose
     * [scheduled_start, scheduled_end) overlaps. Warn-only (non-blocking).
     *
     * @return \Illuminate\Support\Collection<MedsxCase>
     */
    public function conflictsFor(MedsxCase $case)
    {
        if (empty($case->or_room_id) || empty($case->scheduled_start) || empty($case->scheduled_end)) {
            return collect();
        }

        return MedsxCase::forBusiness($case->business_id)
            ->where('or_room_id', $case->or_room_id)
            ->where('id', '!=', $case->id)
            ->whereNotIn('status', ['cancelled', 'postponed'])
            ->whereNotNull('scheduled_start')
            ->whereNotNull('scheduled_end')
            // Overlap test: existing.start < new.end AND existing.end > new.start.
            ->where('scheduled_start', '<', $case->scheduled_end)
            ->where('scheduled_end', '>', $case->scheduled_start)
            ->get();
    }

    /** True if a case's scheduled slot conflicts with another in the same room. */
    public function hasConflict(MedsxCase $case): bool
    {
        return $this->conflictsFor($case)->isNotEmpty();
    }

    // ── MedicalAlertService integration (fail-soft) ─────────────────────────

    /**
     * Raise (or refresh) an idempotent consent_missing alert for the case's
     * patient, keyed on source (medsx_cases, case id) so repeats never duplicate.
     */
    protected function raiseConsentAlert(MedsxCase $case): void
    {
        try {
            if (! class_exists(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class)) {
                return;
            }

            app(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class)->raise([
                'business_id' => (int) $case->business_id,
                'patient_id'  => (int) $case->patient_id,
                'alert_type'  => 'consent_missing',
                'severity'    => 'critical',
                'title'       => __('medsurgery::lang.alert_consent_missing_title', ['case' => $case->case_no]),
                'details'     => __('medsurgery::lang.alert_consent_missing_details'),
                'source_type' => 'medsx_cases',
                'source_id'   => (int) $case->id,
                'created_by'  => auth()->check() ? (int) auth()->id() : ($case->created_by ?? null),
            ]);
        } catch (\Throwable $e) {
            \Log::warning('MedSurgery consent alert raise skipped: ' . $e->getMessage());
        }
    }

    /** Resolve the case's consent_missing alert once consent is present. */
    public function resolveConsentAlert(MedsxCase $case): void
    {
        try {
            if (! class_exists(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class)) {
                return;
            }

            app(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class)
                ->resolveBySource((int) $case->business_id, (int) $case->patient_id, 'medsx_cases', (int) $case->id);
        } catch (\Throwable $e) {
            \Log::warning('MedSurgery consent alert resolve skipped: ' . $e->getMessage());
        }
    }

    // ── op-note attachment (fail-soft, MedDocuments) ────────────────────────

    /**
     * Attach an op-note file to the case's appointment through MedDocuments.
     * FAIL-SOFT (Rule): any absence/error is logged and swallowed; the caller's
     * operation is never blocked. Returns true only when the attach succeeded.
     */
    public function attachOpNote(MedsxCase $case, \Illuminate\Http\UploadedFile $file): bool
    {
        try {
            if (! class_exists(\Modules\MedDocuments\Services\MedDocuments::class)) {
                return false;
            }

            $contactId = $case->contact_id;
            if (empty($contactId) && Schema::hasTable('clc_patients')) {
                $contactId = DB::table('clc_patients')->where('id', $case->patient_id)->value('contact_id');
            }

            app(\Modules\MedDocuments\Services\MedDocuments::class)->attach(
                (int) $case->business_id,
                'operative_notes',
                $file,
                array_filter([
                    'title'       => __('medsurgery::lang.op_note_title', ['case' => $case->case_no]),
                    'entity_type' => class_exists(\Modules\ClinicCore\Entities\ClcAppointment::class) && $case->appointment_id
                        ? \Modules\ClinicCore\Entities\ClcAppointment::class : null,
                    'entity_id'   => $case->appointment_id ?: null,
                    'contact_id'  => $contactId ? (int) $contactId : null,
                    'tags'        => ['surgery', 'op_note'],
                    'user_id'     => auth()->check() ? (int) auth()->id() : null,
                ], fn ($v) => $v !== null)
            );

            return true;
        } catch (\Throwable $e) {
            \Log::warning('MedSurgery op-note attach failed (left as manual step): ' . $e->getMessage());

            return false;
        }
    }
}
