<?php

namespace Modules\MedSurgery\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;
use Modules\MedSurgery\Entities\MedsxCase;
use Modules\MedSurgery\Entities\MedsxChecklist;
use Modules\MedSurgery\Entities\MedsxCount;
use Modules\MedSurgery\Services\ChecklistService;
use Modules\MedSurgery\Services\SurgeryService;
use Yajra\DataTables\Facades\DataTables;

/**
 * Surgical cases (SIFA P6-T4) — DataTable list, create/edit, a tabbed detail
 * screen (overview/team, checklists, counts, post-op) and the operations
 * (transitions, checklist tick+sign, counts, op-note attach) that run through
 * SurgeryService / ChecklistService.
 *
 * Guards: medsx.case.manage for viewing/editing; medsx.case.operate for status
 * transitions; medsx.checklist.sign for signing a checklist phase.
 */
class CaseController extends MedSurgeryBaseController
{
    // ── list / create / edit ────────────────────────────────────────────────

    public function index(Request $request)
    {
        $this->guard('medsx.case.manage');

        if ($request->ajax()) {
            $query = MedsxCase::forBusiness($this->businessId())
                ->select([
                    'id', 'case_no', 'patient_id', 'primary_surgeon_id', 'or_room_id',
                    'priority', 'status', 'scheduled_date', 'scheduled_start', 'consent_id',
                ]);

            if ($request->filled('status')) {
                $query->where('status', $request->get('status'));
            }

            return DataTables::of($query)
                ->addColumn('patient_name', fn ($r) => $this->patientName((int) $r->patient_id))
                ->addColumn('surgeon_name', fn ($r) => $this->doctorName($r->primary_surgeon_id))
                ->addColumn('room_name', fn ($r) => $this->roomName($r->or_room_id))
                ->editColumn('priority', fn ($r) => __('medsurgery::lang.priority_' . $r->priority))
                ->editColumn('status', fn ($r) => '<span class="label ' . $this->statusClass($r->status) . '">'
                    . __('medsurgery::lang.status_' . $r->status) . '</span>')
                ->editColumn('scheduled_date', fn ($r) => $r->scheduled_date
                    ? \Carbon\Carbon::parse($r->scheduled_date)->format('Y-m-d') : '—')
                ->addColumn('action', function ($r) {
                    return '<a class="btn btn-xs btn-primary" href="'
                        . route('medsx.cases.show', $r->id) . '"><i class="fa fa-eye"></i> '
                        . __('medsurgery::lang.open') . '</a>';
                })
                ->rawColumns(['status', 'action'])
                ->make(true);
        }

        return view('medsurgery::cases.index', [
            'statuses' => MedsxCase::STATUSES,
        ]);
    }

    public function create()
    {
        $this->guard('medsx.case.manage');

        return view('medsurgery::cases.form', array_merge($this->formData(), [
            'case' => new MedsxCase(),
        ]));
    }

    public function store(Request $request, ChecklistService $checklists)
    {
        $this->guard('medsx.case.manage');
        $data = $this->validated($request);

        try {
            $businessId = $this->businessId();
            $data['business_id'] = $businessId;
            $data['case_no'] = MedsxCase::nextCaseNo($businessId);
            $data['created_by'] = $this->userId();
            $data['status'] = 'scheduled';

            // Backfill contact_id from the patient when not supplied.
            if (empty($data['contact_id']) && Schema::hasTable('clc_patients')) {
                $data['contact_id'] = DB::table('clc_patients')
                    ->where('id', $data['patient_id'])->value('contact_id');
            }

            $case = MedsxCase::create($data);

            // Seed the three WHO checklist phases with their default items.
            $checklists->seedForCase($case);

            return redirect()->route('medsx.cases.show', $case->id)
                ->with('status', ['success' => 1, 'msg' => __('medsurgery::lang.case_saved')]);
        } catch (\Exception $e) {
            \Log::emergency($e->getMessage());

            return back()->withInput()->with('status', ['success' => 0, 'msg' => __('messages.something_went_wrong')]);
        }
    }

    public function show($id)
    {
        $this->guard('medsx.case.manage');

        $case = MedsxCase::forBusiness($this->businessId())
            ->with(['checklists', 'counts'])
            ->findOrFail($id);

        /** @var SurgeryService $service */
        $service = app(SurgeryService::class);

        return view('medsurgery::cases.show', [
            'case'         => $case,
            'patientName'  => $this->patientName((int) $case->patient_id),
            'surgeonName'  => $this->doctorName($case->primary_surgeon_id),
            'roomName'     => $this->roomName($case->or_room_id),
            'consentTitle' => $this->consentTitle($case->consent_id),
            'allergies'    => $this->allergies((int) $case->patient_id),
            'conflicts'    => $service->conflictsFor($case),
            'rooms'        => $this->rooms(),
            'doctors'      => $this->doctors(),
            'consents'     => $this->consentsForCase($case),
            'checklistByPhase' => $case->checklists->keyBy('phase'),
            'countsByType' => $case->counts->keyBy('count_type'),
            'countTypes'   => MedsxCount::COUNT_TYPES,
            'phases'       => MedsxChecklist::PHASES,
        ]);
    }

    /** Update editable fields (team, procedures, scheduling, post-op record). */
    public function update(Request $request, $id)
    {
        $this->guard('medsx.case.manage');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);
        $section = (string) $request->get('section', 'overview');

        try {
            if ($section === 'postop') {
                $case->update($this->validatedPostOp($request));
            } else {
                $case->update($this->validated($request, $case));

                // Setting consent resolves a lingering consent_missing alert.
                if ($case->consent_id) {
                    app(SurgeryService::class)->resolveConsentAlert($case);
                }
            }

            return $this->ok(__('medsurgery::lang.case_saved'));
        } catch (ValidationException $e) {
            return response()->json(['success' => false, 'msg' => collect($e->errors())->flatten()->first()], 422);
        } catch (\Exception $e) {
            \Log::emergency($e->getMessage());

            return $this->fail();
        }
    }

    // ── operations ────────────────────────────────────────────────────────

    /** Status transition (consent + count gates enforced in the service). */
    public function transition(Request $request, $id, SurgeryService $service)
    {
        $this->guard('medsx.case.operate');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);
        $target = (string) $request->get('target');

        if (! in_array($target, MedsxCase::STATUSES, true)) {
            return $this->fail(__('medsurgery::lang.invalid_action'));
        }

        try {
            $service->transition($case, $target, [
                'cancellation_reason' => $request->get('cancellation_reason'),
                'discrepancy_note'    => $request->get('discrepancy_note'),
            ]);

            return $this->ok(__('medsurgery::lang.case_updated'));
        } catch (ValidationException $e) {
            return response()->json(['success' => false, 'msg' => collect($e->errors())->flatten()->first()], 422);
        } catch (\Exception $e) {
            \Log::emergency($e->getMessage());

            return $this->fail();
        }
    }

    /** Tick/untick a checklist phase's items (does not sign). */
    public function checklistItems(Request $request, $id, $phase, ChecklistService $checklists)
    {
        $this->guard('medsx.case.manage');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);
        $checklist = MedsxChecklist::where('case_id', $case->id)->where('phase', $phase)->firstOrFail();

        $checked = (array) $request->get('checked', []);
        $checklists->updateItems($checklist, $checked, $this->userId());

        return $this->ok(__('medsurgery::lang.checklist_saved'));
    }

    /** Sign off a checklist phase (all items must be checked). */
    public function checklistSign(Request $request, $id, $phase, ChecklistService $checklists)
    {
        $this->guard('medsx.checklist.sign');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);
        $checklist = MedsxChecklist::where('case_id', $case->id)->where('phase', $phase)->firstOrFail();

        // Persist any pending ticks sent with the sign request.
        if ($request->has('checked')) {
            $checklists->updateItems($checklist, (array) $request->get('checked', []), $this->userId());
            $checklist = $checklist->fresh();
        }

        if (! $checklists->sign($checklist, (int) $this->userId())) {
            return response()->json(['success' => false, 'msg' => __('medsurgery::lang.checklist_incomplete')], 422);
        }

        return $this->ok(__('medsurgery::lang.checklist_signed'));
    }

    /** Save (upsert) a count row for a type. discrepancy is computed on save. */
    public function saveCount(Request $request, $id)
    {
        $this->guard('medsx.case.operate');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);

        $data = $this->validate($request, [
            'count_type'  => 'required|in:sponge,instrument,needle',
            'pre_count'   => 'required|integer|min:0',
            'close_count' => 'nullable|integer|min:0',
            'final_count' => 'nullable|integer|min:0',
        ]);

        try {
            $count = MedsxCount::firstOrNew([
                'case_id'    => $case->id,
                'count_type' => $data['count_type'],
            ]);
            $count->fill([
                'business_id' => $case->business_id,
                'pre_count'   => $data['pre_count'],
                'close_count' => $data['close_count'] ?? null,
                'final_count' => $data['final_count'] ?? null,
                'counted_by'  => $this->userId(),
                'counted_at'  => now(),
            ]);
            $count->save(); // boot hook recomputes discrepancy

            return $this->ok(__('medsurgery::lang.count_saved'), ['discrepancy' => (bool) $count->discrepancy]);
        } catch (\Exception $e) {
            \Log::emergency($e->getMessage());

            return $this->fail();
        }
    }

    /** Attach an op-note file (fail-soft via MedDocuments). */
    public function attachOpNote(Request $request, $id, SurgeryService $service)
    {
        $this->guard('medsx.case.manage');

        $case = MedsxCase::forBusiness($this->businessId())->findOrFail($id);

        $this->validate($request, ['op_note' => 'required|file|max:20480']);

        $ok = $service->attachOpNote($case, $request->file('op_note'));

        return $ok
            ? $this->ok(__('medsurgery::lang.op_note_attached'))
            : $this->fail(__('medsurgery::lang.op_note_unavailable'));
    }

    // ── validation ──────────────────────────────────────────────────────────

    private function validated(Request $request, ?MedsxCase $case = null): array
    {
        $data = $this->validate($request, [
            'patient_id'           => ($case ? 'sometimes|' : '') . 'required|integer',
            'contact_id'           => 'nullable|integer',
            'appointment_id'       => 'nullable|integer',
            'primary_surgeon_id'   => 'nullable|integer',
            'assistant_ids'        => 'nullable|array',
            'assistant_ids.*'      => 'integer',
            'anesthetist_name'     => 'nullable|string|max:191',
            'scrub_nurse_id'       => 'nullable|integer',
            'circulating_nurse_id' => 'nullable|integer',
            'procedures'           => 'nullable|array',
            'procedures.*.name'      => 'nullable|string|max:191',
            'procedures.*.cpt_code'  => 'nullable|string|max:20',
            'procedures.*.laterality' => 'nullable|in:l,r,bilateral,na',
            'diagnosis'            => 'nullable|string',
            'asa_class'            => 'nullable|integer|min:1|max:6',
            'priority'            => 'required|in:elective,urgent,emergency',
            'anesthesia_type'    => 'nullable|in:general,spinal,epidural,regional,local,sedation,combined',
            'or_room_id'         => 'nullable|integer',
            'scheduled_date'     => 'nullable|date',
            'scheduled_start'    => 'nullable|date',
            'scheduled_end'      => 'nullable|date',
            'consent_id'         => 'nullable|integer',
        ]);

        // Keep only clean procedure rows (a name is required to persist a row).
        if (! empty($data['procedures'])) {
            $data['procedures'] = array_values(array_filter($data['procedures'], fn ($p) => ! empty($p['name'])));
        }

        return $data;
    }

    private function validatedPostOp(Request $request): array
    {
        $data = $this->validate($request, [
            'findings'        => 'nullable|string',
            'complications'   => 'nullable|string',
            'blood_loss'      => 'nullable|in:minimal,moderate,significant,massive',
            'specimens'       => 'nullable|array',
            'specimens.*.description' => 'nullable|string|max:191',
            'specimens.*.sent_to'     => 'nullable|string|max:60',
            'specimens.*.lab_number'  => 'nullable|string|max:60',
            'implants'        => 'nullable|array',
            'implants.*.name'   => 'nullable|string|max:191',
            'implants.*.serial' => 'nullable|string|max:100',
            'implants.*.lot'    => 'nullable|string|max:100',
        ]);

        if (! empty($data['specimens'])) {
            $data['specimens'] = array_values(array_filter($data['specimens'], fn ($s) => ! empty($s['description'])));
        }
        if (! empty($data['implants'])) {
            $data['implants'] = array_values(array_filter($data['implants'], fn ($i) => ! empty($i['name'])));
        }

        return $data;
    }

    // ── reference-data helpers (fail-soft against optional modules) ─────────

    private function formData(): array
    {
        return [
            'priorities'      => MedsxCase::PRIORITIES,
            'anesthesiaTypes' => MedsxCase::ANESTHESIA_TYPES,
            'lateralities'    => MedsxCase::LATERALITIES,
            'patients'        => $this->patients(),
            'doctors'         => $this->doctors(),
            'rooms'           => $this->rooms(),
        ];
    }

    private function patients(): array
    {
        if (! Schema::hasTable('clc_patients')) {
            return [];
        }

        return DB::table('clc_patients')
            ->leftJoin('contacts', 'clc_patients.contact_id', '=', 'contacts.id')
            ->where('clc_patients.business_id', $this->businessId())
            ->orderBy('contacts.name')
            ->limit(500)
            ->pluck(DB::raw("COALESCE(contacts.name, clc_patients.patient_name, CONCAT('Patient #', clc_patients.id))"), 'clc_patients.id')
            ->toArray();
    }

    private function doctors(): array
    {
        if (! Schema::hasTable('clc_doctors')) {
            return [];
        }

        return DB::table('clc_doctors')
            ->where('business_id', $this->businessId())
            ->where('status', 'active')
            ->orderBy('name')
            ->pluck('name', 'id')
            ->toArray();
    }

    /** OR rooms — org_rooms rows with room_type=operating (empty if none/absent). */
    private function rooms(): array
    {
        if (! Schema::hasTable('org_rooms')) {
            return [];
        }

        return DB::table('org_rooms')
            ->where('business_id', $this->businessId())
            ->where('room_type', config('medsurgery.or_room_type', 'operating'))
            ->orderBy('name')
            ->pluck('name', 'id')
            ->toArray();
    }

    /** Consents already issued for this case's patient/appointment, if any. */
    private function consentsForCase(MedsxCase $case): array
    {
        if (! Schema::hasTable('clc_consents')) {
            return [];
        }

        $q = DB::table('clc_consents')->where('business_id', $case->business_id);
        $q->where(function ($w) use ($case) {
            $w->where('patient_id', $case->patient_id);
            if ($case->appointment_id) {
                $w->orWhere('appointment_id', $case->appointment_id);
            }
        });

        return $q->orderByDesc('id')->limit(50)->pluck('title', 'id')->toArray();
    }

    private function patientName(int $patientId): string
    {
        if (! Schema::hasTable('clc_patients')) {
            return 'Patient #' . $patientId;
        }
        $name = DB::table('clc_patients')
            ->leftJoin('contacts', 'clc_patients.contact_id', '=', 'contacts.id')
            ->where('clc_patients.id', $patientId)
            ->value(DB::raw('COALESCE(contacts.name, clc_patients.patient_name)'));

        return $name ?: ('Patient #' . $patientId);
    }

    private function doctorName($doctorId): string
    {
        if (empty($doctorId) || ! Schema::hasTable('clc_doctors')) {
            return $doctorId ? ('#' . $doctorId) : '—';
        }

        return (string) (DB::table('clc_doctors')->where('id', $doctorId)->value('name') ?: ('#' . $doctorId));
    }

    private function roomName($roomId): string
    {
        if (empty($roomId) || ! Schema::hasTable('org_rooms')) {
            return $roomId ? ('#' . $roomId) : '—';
        }

        return (string) (DB::table('org_rooms')->where('id', $roomId)->value('name') ?: ('#' . $roomId));
    }

    private function consentTitle($consentId): ?string
    {
        if (empty($consentId) || ! Schema::hasTable('clc_consents')) {
            return null;
        }

        return DB::table('clc_consents')->where('id', $consentId)->value('title');
    }

    /** Active allergies for the patient (displayed on the case, spec reuse). */
    private function allergies(int $patientId): array
    {
        if (! Schema::hasTable('clc_patient_allergies')) {
            return [];
        }

        return DB::table('clc_patient_allergies')
            ->where('business_id', $this->businessId())
            ->where('patient_id', $patientId)
            ->where('status', 'active')
            ->orderByRaw("FIELD(severity,'life_threatening','severe','moderate','mild')")
            ->get(['allergen', 'severity', 'reaction'])
            ->map(fn ($a) => (array) $a)
            ->toArray();
    }

    private function statusClass(string $status): string
    {
        return [
            'scheduled' => 'label-default',
            'pre_op'    => 'label-info',
            'in_or'     => 'label-warning',
            'post_op'   => 'label-primary',
            'completed' => 'label-success',
            'cancelled' => 'label-danger',
            'postponed' => 'label-default',
        ][$status] ?? 'label-default';
    }
}
