<?php

namespace Modules\ClinicCore\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\ClinicCore\Entities\ClcAppointment;
use Modules\ClinicCore\Entities\ClcAuthorizedVet;
use Modules\ClinicCore\Entities\ClcControlledDailySeal;
use Modules\ClinicCore\Entities\ClcControlledMovement;
use Modules\ClinicCore\Entities\ClcControlledReconciliation;
use Modules\ClinicCore\Entities\ClcDoctor;
use Modules\ClinicCore\Entities\ClcMedicine;
use Yajra\DataTables\Facades\DataTables;

/**
 * The controlled-drug register page. Lists every IN/OUT movement of every
 * scheduled medicine with its running balance, and lets authorised users
 * append new rows. Rows are insert-only — see ClcControlledMovement.
 */
class ClcControlledRegisterController extends Controller
{
    use \Modules\ClinicCore\Http\Controllers\Traits\LogsClcActivity;

    /** This clinic type must handle controlled substances (capability + override). */
    private function capabilityGuard(): void
    {
        $bizId = (int) request()->session()->get('user.business_id');
        if (!\Modules\ClinicCore\Entities\Services\ClinicCapabilities::businessHas($bizId, 'controlled_drugs')) {
            abort(403, 'The controlled-drug register is not enabled for this clinic type.');
        }
    }

    private function viewerGuard(): void
    {
        if (!auth()->user()->can('clinic.manage_controlled')) {
            abort(403, 'Unauthorized action.');
        }
        $this->capabilityGuard();
    }

    private function loggerGuard(): void
    {
        if (!auth()->user()->can('clinic.log_controlled')
            && !auth()->user()->can('clinic.manage_controlled')) {
            abort(403, 'Unauthorized action.');
        }
        $this->capabilityGuard();
    }

    public function index(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');

        $controlledMedicines = ClcMedicine::where('business_id', $businessId)
            ->controlled()->active()->orderBy('name')->get(['id', 'name', 'unit', 'controlled_schedule']);

        $doctors = ClcDoctor::where('business_id', $businessId)->active()->orderBy('name')->get(['id', 'name']);

        // Phase-2b: surface POS dispenses still missing a verified vet + witness.
        $unverifiedCount = ClcControlledMovement::where('business_id', $businessId)
            ->where('reason_code', 'dispense_unverified')->count();

        // Phase-2: also surface vets with permits expired or expiring in <= 30d.
        $expiringVets = \Modules\ClinicCore\Entities\ClcAuthorizedVet::with('doctor:id,name')
            ->where('business_id', $businessId)
            ->whereNotNull('valid_until')
            ->where('valid_until', '<=', now()->addDays(30))
            ->orderBy('valid_until')->get();

        // Active users for witness picker (used by the resolve modal).
        $witnesses = \App\User::where('business_id', $businessId)
            ->where('user_type', 'user')->where('status', 'active')
            ->orderBy('first_name')->get(['id', 'first_name', 'last_name']);

        // Phase 3b — latest seal + count of yet-to-be-sealed days, surfaced in
        // the integrity strip so an admin can see at a glance whether the chain
        // is up to date.
        $latestSeal = ClcControlledDailySeal::where('business_id', $businessId)
            ->orderByDesc('seal_date')->first();
        $unsealedDays = \DB::table('clc_controlled_stock_movements')
            ->where('business_id', $businessId)
            ->whereDate('created_at', '<', now()->toDateString())
            ->selectRaw('COUNT(DISTINCT DATE(created_at)) AS n')->value('n')
            - ClcControlledDailySeal::where('business_id', $businessId)->count();
        if ($unsealedDays < 0) $unsealedDays = 0;

        return view('clinicCore::controlled-register.index', [
            'medicines'       => $controlledMedicines,
            'doctors'         => $doctors,
            'reasons'         => ClcControlledMovement::reasonCodes(),
            'schedules'       => ClcMedicine::schedules(),
            'unverifiedCount' => $unverifiedCount,
            'expiringVets'    => $expiringVets,
            'witnesses'       => $witnesses,
            'latestSeal'      => $latestSeal,
            'unsealedDays'    => $unsealedDays,
        ]);
    }

    /** Verify the SHA-256 daily-seal chain end-to-end. */
    public function verifyChain(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');
        return response()->json(ClcControlledDailySeal::verifyChain((int) $businessId));
    }

    /** Manually seal any outstanding days now (otherwise runs nightly). */
    public function sealNow(Request $request)
    {
        if (!auth()->user()->can('clinic.manage_controlled')) {
            abort(403, 'Unauthorized action.');
        }
        $businessId = $request->session()->get('user.business_id');
        $sealed = ClcControlledDailySeal::sealOutstanding((int) $businessId);
        return response()->json(['success' => true, 'sealed' => $sealed, 'count' => count($sealed)]);
    }

    /**
     * Promote a POS-auto-synced "dispense_unverified" row into a verified
     * "dispense" by attaching an issuing vet + witness. Auth-checks the vet
     * against clc_authorized_vets just like a fresh dispense.
     */
    public function resolveUnverified(Request $request, $id)
    {
        $this->loggerGuard();
        $businessId = $request->session()->get('user.business_id');

        $request->validate([
            'doctor_id'       => 'required|integer',
            'witness_user_id' => 'required|integer',
            'notes'           => 'nullable|string|max:300',
        ]);

        $row = ClcControlledMovement::where('business_id', $businessId)
            ->where('reason_code', 'dispense_unverified')
            ->findOrFail($id);

        $med = $row->medicine;
        $status = \Modules\ClinicCore\Entities\ClcAuthorizedVet::checkAuthorization(
            (int) $businessId, (int) $request->doctor_id, $med->controlled_schedule ?? null
        );
        if ($status !== 'ok') {
            $msg = match ($status) {
                'no_permit'      => 'The selected doctor has no authorisation on file.',
                'wrong_schedule' => 'The selected doctor is not authorised for this schedule.',
                'expired'        => "The selected doctor's permit has expired.",
                default          => 'Doctor authorisation check failed.',
            };
            return response()->json(['success' => false, 'msg' => $msg], 422);
        }

        $row->update([
            'reason_code'     => 'dispense',
            'doctor_id'       => (int) $request->doctor_id,
            'witness_user_id' => (int) $request->witness_user_id,
            'notes'           => trim(($row->notes ? $row->notes . ' · ' : '') . 'Resolved: ' . ($request->notes ?: 'verified by ' . auth()->user()->first_name)),
        ]);
        $this->drmLog('update', $row, ['action' => 'resolved_unverified_dispense']);

        return response()->json(['success' => true, 'msg' => 'Row promoted to verified dispense.']);
    }

    public function list(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');

        $query = ClcControlledMovement::with([
                'medicine:id,name,unit,controlled_schedule',
                'pet:id,patient_name', 'owner:id,name', 'doctor:id,name',
                'witness:id,first_name,last_name', 'createdBy:id,first_name,last_name',
            ])
            ->where('business_id', $businessId)
            ->orderByDesc('id');

        if ($request->filled('medicine_id'))   $query->where('medicine_id',   $request->medicine_id);
        if ($request->filled('reason_code'))   $query->where('reason_code',   $request->reason_code);
        if ($request->filled('schedule')) {
            $query->whereHas('medicine', fn($q) => $q->where('controlled_schedule', $request->schedule));
        }
        if ($request->filled('date_from'))     $query->whereDate('created_at', '>=', $request->date_from);
        if ($request->filled('date_to'))       $query->whereDate('created_at', '<=', $request->date_to);
        if ($request->filled('batch_no'))      $query->where('batch_no', $request->batch_no);

        $reasons   = ClcControlledMovement::reasonCodes();
        $schedules = ClcMedicine::schedules();

        return DataTables::of($query)
            ->editColumn('created_at', fn($r) => optional($r->created_at)->format('Y-m-d H:i'))
            ->addColumn('medicine_col', function ($r) use ($schedules) {
                $name = optional($r->medicine)->name ?? '—';
                $sch  = optional($r->medicine)->controlled_schedule;
                $badge = $sch
                    ? ' <span class="label label-' . ($schedules[$sch]['color'] ?? 'default') . '" style="font-size:9px;">' . e($schedules[$sch]['label'] ?? $sch) . '</span>'
                    : '';
                return '<strong>' . e($name) . '</strong>' . $badge;
            })
            ->addColumn('qty_col', function ($r) {
                $sign  = $r->direction === 'in'  ? '+' : ($r->direction === 'out' ? '−' : '±');
                $color = $r->direction === 'in'  ? '#15803d' : ($r->direction === 'out' ? '#b91c1c' : '#475569');
                $qty   = rtrim(rtrim(number_format((float) $r->qty, 3, '.', ''), '0'), '.');
                return '<strong style="color:' . $color . ';">' . $sign . $qty . '</strong>'
                     . ' <span class="text-muted" style="font-size:11px;">' . e($r->unit ?: '') . '</span>';
            })
            ->addColumn('balance_col', function ($r) {
                return rtrim(rtrim(number_format((float) $r->balance_after, 3, '.', ''), '0'), '.');
            })
            ->addColumn('reason_col', function ($r) use ($reasons) {
                if ($r->reason_code === 'dispense_unverified') {
                    return '<span class="label label-danger" title="POS dispense without verified vet">'
                         . '<i class="fa fa-exclamation-triangle"></i> Unverified</span>';
                }
                $cfg = $reasons[$r->reason_code] ?? null;
                return $cfg
                    ? '<span class="label label-' . $cfg['color'] . '">' . e($cfg['label']) . '</span>'
                    : e($r->reason_code);
            })
            ->addColumn('resolve_col', function ($r) {
                if ($r->reason_code !== 'dispense_unverified') return '';
                return '<button class="btn btn-xs btn-warning cr-resolve" data-id="' . $r->id
                     . '" data-med="' . e(optional($r->medicine)->name ?? '') . '">'
                     . '<i class="fa fa-user-check"></i> Resolve</button>';
            })
            ->addColumn('patient_col', function ($r) {
                $patient   = optional($r->pet)->patient_name;
                $owner = optional($r->owner)->name;
                if (!$patient && !$owner) return '—';
                return '<strong>' . e($patient ?: '—') . '</strong>'
                     . ($owner ? '<br><span class="text-muted" style="font-size:11px;">' . e($owner) . '</span>' : '');
            })
            ->addColumn('signoff_col', function ($r) {
                $doc = optional($r->doctor)->name;
                $wit = trim((optional($r->witness)->first_name ?? '') . ' ' . (optional($r->witness)->last_name ?? ''));
                $by  = trim((optional($r->createdBy)->first_name ?? '') . ' ' . (optional($r->createdBy)->last_name ?? ''));
                $html = $doc ? '<strong>' . e($doc) . '</strong>' : '';
                if ($wit) $html .= '<br><span class="text-muted" style="font-size:11px;">witness: ' . e($wit) . '</span>';
                if ($by)  $html .= '<br><span class="text-muted" style="font-size:10px;">by ' . e($by) . '</span>';
                return $html ?: '—';
            })
            ->rawColumns(['medicine_col', 'qty_col', 'reason_col', 'patient_col', 'signoff_col', 'resolve_col'])
            ->make(true);
    }

    /**
     * Statutory per-medicine register print (date · owner · pet · qty IN ·
     * qty OUT · balance · vet · witness · signature). One page per medicine,
     * filtered by date range + optional schedule.
     *
     * Query params: medicine_id (optional, repeatable), schedule, from, to
     */
    public function print(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');

        $business = \App\Business::findOrFail($businessId);

        $medQuery = ClcMedicine::where('business_id', $businessId)->controlled()->active();
        if ($request->filled('schedule'))    $medQuery->where('controlled_schedule', $request->schedule);
        if ($request->filled('medicine_id')) $medQuery->where('id', $request->medicine_id);
        $medicines = $medQuery->orderBy('name')->get();

        $from = $request->filled('from') ? \Carbon\Carbon::parse($request->from)->startOfDay() : now()->startOfMonth();
        $to   = $request->filled('to')   ? \Carbon\Carbon::parse($request->to)->endOfDay()     : now()->endOfDay();

        // Pre-load all movements grouped by medicine in the requested range.
        $movements = ClcControlledMovement::with([
                'pet:id,patient_name', 'owner:id,name', 'doctor:id,name',
                'witness:id,first_name,last_name', 'createdBy:id,first_name,last_name',
            ])
            ->where('business_id', $businessId)
            ->whereIn('medicine_id', $medicines->pluck('id'))
            ->whereBetween('created_at', [$from, $to])
            ->orderBy('medicine_id')
            ->orderBy('id')
            ->get()
            ->groupBy('medicine_id');

        // For each medicine, also compute the opening balance (running balance
        // immediately before the date range) so the printout reconciles.
        $openingBalances = [];
        foreach ($medicines as $med) {
            $row = ClcControlledMovement::where('business_id', $businessId)
                ->where('medicine_id', $med->id)
                ->where('created_at', '<', $from)
                ->orderByDesc('id')
                ->first(['balance_after']);
            $openingBalances[$med->id] = $row->balance_after ?? 0.0;
        }

        return view('clinicCore::controlled-register.print', [
            'business'        => $business,
            'medicines'       => $medicines,
            'movements'       => $movements,
            'openingBalances' => $openingBalances,
            'from'            => $from,
            'to'              => $to,
            'schedules'       => ClcMedicine::schedules(),
            'reasons'         => ClcControlledMovement::reasonCodes(),
        ]);
    }

    /** Show the reconciliation / stocktake form for one medicine + batch. */
    public function reconcile(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');

        $medicines = ClcMedicine::where('business_id', $businessId)
            ->controlled()->active()->orderBy('name')->get(['id', 'name', 'unit', 'controlled_schedule']);

        $witnesses = \App\User::where('business_id', $businessId)
            ->where('user_type', 'user')->where('status', 'active')
            ->orderBy('first_name')->get(['id', 'first_name', 'last_name']);

        $recent = ClcControlledReconciliation::with(['medicine:id,name,unit', 'createdBy:id,first_name,last_name', 'witness:id,first_name,last_name'])
            ->where('business_id', $businessId)
            ->orderByDesc('id')->limit(20)->get();

        return view('clinicCore::controlled-register.reconcile', compact('medicines', 'witnesses', 'recent'));
    }

    /** Return current system-expected balance for a medicine (optionally batch). */
    public function expectedBalance(Request $request)
    {
        $this->viewerGuard();
        $businessId = $request->session()->get('user.business_id');

        $request->validate([
            'medicine_id' => 'required|integer',
            'batch_no'    => 'nullable|string|max:60',
        ]);

        $bal = ClcControlledMovement::currentBalance(
            (int) $businessId,
            (int) $request->medicine_id,
            $request->filled('batch_no') ? $request->batch_no : null
        );

        return response()->json(['expected' => $bal]);
    }

    /**
     * Record a stocktake. If counted != expected, insert a paired `adjust`
     * movement that brings the running balance into line with the count and
     * back-reference it on the stocktake row.
     */
    public function recordReconciliation(Request $request)
    {
        if (!auth()->user()->can('clinic.manage_controlled')) {
            abort(403, 'Unauthorized action.');
        }

        $request->validate([
            'medicine_id'     => 'required|integer',
            'batch_no'        => 'nullable|string|max:60',
            'counted_qty'     => 'required|numeric|gte:0',
            'witnessed_by'    => 'nullable|integer',
            'notes'           => 'nullable|string|max:500',
        ]);

        $businessId = $request->session()->get('user.business_id');
        $medicine = ClcMedicine::where('business_id', $businessId)
            ->controlled()->findOrFail($request->medicine_id);

        $batch    = trim((string) $request->batch_no) ?: null;
        $counted  = (float) $request->counted_qty;
        $expected = ClcControlledMovement::currentBalance((int) $businessId, $medicine->id, $batch);
        $variance = round($counted - $expected, 3);

        return \DB::transaction(function () use ($request, $businessId, $medicine, $batch, $counted, $expected, $variance) {

            $stocktake = ClcControlledReconciliation::create([
                'business_id'   => $businessId,
                'reference'     => ClcControlledReconciliation::nextReference($businessId),
                'medicine_id'   => $medicine->id,
                'batch_no'      => $batch,
                'expected_qty'  => $expected,
                'counted_qty'   => $counted,
                'variance'      => $variance,
                'unit'          => $medicine->unit,
                'notes'         => $request->notes,
                'stocktake_at'  => now(),
                'witnessed_by'  => $request->witnessed_by ?: null,
                'created_by'    => auth()->id(),
            ]);

            $movement = null;
            if (abs($variance) > 0.0005) {
                // Paired adjust row absorbs the variance.
                $movement = ClcControlledMovement::create([
                    'business_id'     => $businessId,
                    'serial_no'       => ClcControlledMovement::nextSerial($businessId),
                    'medicine_id'     => $medicine->id,
                    'direction'       => 'adjust',
                    'qty'             => abs($variance),
                    'unit'            => $medicine->unit,
                    'batch_no'        => $batch,
                    'balance_after'   => $counted,
                    'reason_code'     => $variance > 0 ? 'correction' : 'adjust',
                    'witness_user_id' => $request->witnessed_by ?: null,
                    'stocktake_id'    => $stocktake->id,
                    'notes'           => 'Stocktake ' . $stocktake->reference . ' · variance '
                                       . ($variance > 0 ? '+' : '') . $variance
                                       . ($request->notes ? ' · ' . $request->notes : ''),
                    'created_by'      => auth()->id(),
                ]);

                $stocktake->update(['movement_id' => $movement->id]);
                $this->drmLog('create', $movement, ['source' => 'stocktake', 'stocktake_id' => $stocktake->id]);
            }
            $this->drmLog('create', $stocktake);

            return response()->json([
                'success'  => true,
                'msg'      => abs($variance) > 0.0005
                    ? "Stocktake recorded. Variance of " . ($variance > 0 ? '+' : '') . $variance . " was absorbed by adjustment "
                      . ($movement ? $movement->serial_no : '')
                    : "Stocktake recorded — count matches the register.",
                'reference'    => $stocktake->reference,
                'expected'     => $expected,
                'counted'      => $counted,
                'variance'     => $variance,
                'movement_ref' => $movement ? $movement->serial_no : null,
            ]);
        });
    }

    public function store(Request $request)
    {
        $this->loggerGuard();
        $businessId = $request->session()->get('user.business_id');

        $request->validate([
            'medicine_id' => 'required|integer',
            'reason_code' => 'required|string',
            'qty'         => 'required|numeric|gt:0',
            'batch_no'    => 'nullable|string|max:60',
            'expiry_date' => 'nullable|date',
            'unit'        => 'nullable|string|max:30',
            'patient_id'      => 'nullable|integer',
            'contact_id'  => 'nullable|integer',
            'doctor_id'   => 'nullable|integer',
            'witness_user_id' => 'nullable|integer',
            'notes'       => 'nullable|string|max:500',
        ]);

        $reasons = ClcControlledMovement::reasonCodes();
        if (!isset($reasons[$request->reason_code])) {
            return response()->json(['success' => false, 'msg' => 'Unknown reason code.'], 422);
        }
        $direction = $reasons[$request->reason_code]['direction'];

        $medicine = ClcMedicine::where('business_id', $businessId)
            ->controlled()->findOrFail($request->medicine_id);

        // Phase-2 authorisation gate: only authorised vets may dispense, and
        // their permit must still cover the medicine's schedule and not be expired.
        if ($direction === 'out' && $request->reason_code === 'dispense') {
            $status = ClcAuthorizedVet::checkAuthorization(
                (int) $businessId,
                $request->doctor_id ? (int) $request->doctor_id : null,
                $medicine->controlled_schedule
            );
            if ($status !== 'ok') {
                $msg = match ($status) {
                    'no_permit'      => 'No issuing doctor selected, or the selected doctor is not on the authorised-doctors list. Add them under Settings → Authorised Doctors.',
                    'wrong_schedule' => 'The selected doctor is authorised, but not for this schedule (' . ($medicine->schedule_label ?? '—') . ').',
                    'expired'        => "The selected doctor's permit has expired. Renew it under Settings → Authorised Doctors.",
                    default          => 'Doctor authorisation check failed.',
                };
                return response()->json(['success' => false, 'msg' => $msg], 422);
            }
        }

        return \DB::transaction(function () use ($request, $businessId, $direction, $medicine) {
            $batch = trim((string) $request->batch_no) ?: null;
            $prevBalance = ClcControlledMovement::currentBalance($businessId, $medicine->id, $batch);
            $qty = (float) $request->qty;

            $newBalance = match ($direction) {
                'in'     => $prevBalance + $qty,
                'out'    => $prevBalance - $qty,
                default  => $prevBalance,    // adjust/correction handled separately
            };

            if ($direction === 'out' && $newBalance < 0) {
                return response()->json([
                    'success' => false,
                    'msg' => "Not enough stock — current balance for batch '" . ($batch ?: '—') . "' is "
                        . rtrim(rtrim(number_format($prevBalance, 3, '.', ''), '0'), '.') . '.',
                ], 422);
            }

            $row = ClcControlledMovement::create([
                'business_id'     => $businessId,
                'serial_no'       => ClcControlledMovement::nextSerial($businessId),
                'medicine_id'     => $medicine->id,
                'direction'       => $direction,
                'qty'             => $qty,
                'unit'            => $request->unit ?: $medicine->unit,
                'batch_no'        => $batch,
                'expiry_date'     => $request->expiry_date,
                'balance_after'   => $newBalance,
                'reason_code'     => $request->reason_code,
                'patient_id'          => $request->patient_id ?: null,
                'contact_id'      => $request->contact_id ?: null,
                'appointment_id'  => $request->appointment_id ?: null,
                'prescription_id' => $request->prescription_id ?: null,
                'doctor_id'       => $request->doctor_id ?: null,
                'witness_user_id' => $request->witness_user_id ?: null,
                'notes'           => $request->notes,
                'created_by'      => auth()->id(),
            ]);
            $this->drmLog('create', $row);

            return response()->json([
                'success' => true,
                'msg'     => "Logged " . $row->serial_no . '.',
            ]);
        });
    }
}
