<?php

namespace Modules\MedPortal\Services;

use Modules\MedPortal\Entities\MportalRecordRelease;

/**
 * RecordAccessService — the single source of truth for WHAT a portal patient can
 * see (SIFA Phase 8, patient_portal → medical_records).
 *
 * Spec rule: clinical notes are visible ONLY after provider release; labs,
 * imaging, prescriptions and vitals are visible by default once the portal is
 * linked. Config `medportal.release_defaults` holds those defaults; a row in
 * mportal_record_releases OVERRIDES the default per (patient, record_type). A
 * `full` release row that is `released=true` reveals EVERYTHING (master switch).
 *
 * The gate is pure/read-only over mportal_record_releases — it never touches
 * ClinicCore tables. Controllers/patient views call visibleRecords() /
 * canView() before querying ClinicCore.
 */
class RecordAccessService
{
    /** Record types the portal understands (excluding the `full` master toggle). */
    public const TYPES = ['clinical_note', 'lab_result', 'imaging_report', 'prescription', 'vitals'];

    /**
     * Map of record_type => bool (visible?) for a patient, applying config
     * defaults then per-patient overrides then the `full` master switch.
     *
     * @return array<string,bool>
     */
    public function visibleRecords(int $businessId, int $patientId): array
    {
        $defaults = (array) config('medportal.release_defaults', []);

        $visible = [];
        foreach (self::TYPES as $type) {
            $visible[$type] = (bool) ($defaults[$type] ?? false);
        }

        $overrides = MportalRecordRelease::forBusiness($businessId)
            ->forPatient($patientId)
            ->get()
            ->keyBy('record_type');

        // `full` master toggle: if explicitly released, reveal all types.
        if (($overrides['full'] ?? null) && $overrides['full']->released) {
            foreach (self::TYPES as $type) {
                $visible[$type] = true;
            }
        }

        // Per-type overrides win over both default and the `full` toggle.
        foreach (self::TYPES as $type) {
            if (isset($overrides[$type])) {
                $visible[$type] = (bool) $overrides[$type]->released;
            }
        }

        return $visible;
    }

    /** Is one record type visible to this patient? */
    public function canView(int $businessId, int $patientId, string $recordType): bool
    {
        return $this->visibleRecords($businessId, $patientId)[$recordType] ?? false;
    }

    /**
     * Release a record type to the patient (idempotent upsert). Passing
     * `clinical_note` is the primary spec use-case ("notes after provider
     * release"); `full` reveals everything.
     */
    public function release(int $businessId, int $patientId, string $recordType, ?int $userId = null, ?string $notes = null): MportalRecordRelease
    {
        return $this->setReleased($businessId, $patientId, $recordType, true, $userId, $notes);
    }

    /** Un-release (hide) a record type from the patient. */
    public function unrelease(int $businessId, int $patientId, string $recordType, ?int $userId = null, ?string $notes = null): MportalRecordRelease
    {
        return $this->setReleased($businessId, $patientId, $recordType, false, $userId, $notes);
    }

    private function setReleased(int $businessId, int $patientId, string $recordType, bool $released, ?int $userId, ?string $notes): MportalRecordRelease
    {
        if (! in_array($recordType, MportalRecordRelease::RECORD_TYPES, true)) {
            throw new \InvalidArgumentException("Unknown record type: {$recordType}");
        }

        $row = MportalRecordRelease::firstOrNew([
            'business_id' => $businessId,
            'patient_id'  => $patientId,
            'record_type' => $recordType,
        ]);

        $row->released    = $released;
        $row->released_by = $released ? $userId : $row->released_by;
        $row->released_at = $released ? now() : null;
        if ($notes !== null) {
            $row->notes = $notes;
        }
        $row->save();

        return $row;
    }

    /**
     * Count of patients with at least one type still UNreleased that is normally
     * gated (currently: clinical_note). Feeds the staff dashboard "pending
     * release" tile. Fail-soft: returns 0 if ClinicCore isn't present.
     */
    public function pendingReleaseCount(int $businessId): int
    {
        if (! \Illuminate\Support\Facades\Schema::hasTable('clc_patients')) {
            return 0;
        }

        $totalPatients = \Illuminate\Support\Facades\DB::table('clc_patients')
            ->where('business_id', $businessId)
            ->count();

        // Patients that HAVE a clinical_note release row set to true.
        $released = MportalRecordRelease::forBusiness($businessId)
            ->where('record_type', 'clinical_note')
            ->where('released', true)
            ->distinct()
            ->count('patient_id');

        return max(0, $totalPatients - $released);
    }
}
