<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\Http\Response;
use Cake\I18n\FrozenDate;
use Cake\I18n\FrozenTime;
use Cake\Log\Log;
use Cake\ORM\Query;

class WaitlistController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Authentication.Authentication');

        $this->Waitlist     = $this->fetchTable('Waitlist');
        $this->Participants = $this->fetchTable('Participants');
        $this->Login        = $this->fetchTable('Login');

        $this->viewBuilder()->setLayout('default');
    }

    /* ----------------------- Clinic scoping helpers ----------------------- */

    /**
     * Apply current-clinic visibility to a Waitlist query.
     *
     * Rows are visible if the row's clinician OR participant belongs to the current clinic.
     */
    private function scopeClinic(Query $q): Query
    {
        $clinicId = $this->currentClinicId();
        if (!$clinicId) {
            // No clinic context: show nothing.
            $q->where(['1 = 0']);
            return $q;
        }

        // Join through clinic links (left joins because either side may be null)
        $q
            ->leftJoin(['cu' => 'clinic_users'], 'cu.login_id = Waitlist.clinician_id')
            ->leftJoin(['pc' => 'participants_clinics'], 'pc.participant_id = Waitlist.participant_id')
            ->where(['OR' => [
                'cu.clinic_id' => $clinicId,
                'pc.clinic_id' => $clinicId,
            ]]);

        return $q;
    }

    /** Fetch a single waitlist row restricted to current clinic or 404. */
    private function loadScoped(int $id)
    {
        $q = $this->Waitlist->find()
            ->where(['Waitlist.id' => $id])
            ->contain(['Participants','Login']);

        $row = $this->scopeClinic($q)->first();
        if (!$row) {
            throw new \Cake\Http\Exception\NotFoundException('Waitlist item not found.');
        }
        return $row;
    }

    /** Ensure a clinician belongs to the current clinic (if provided). */
    private function assertClinicianInClinic(?int $clinicianId): bool
    {
        if ($clinicianId === null) return true;
        $clinicId = $this->currentClinicId();
        if (!$clinicId) return false;

        $ok = $this->Login->find()
            ->select(['Login.id'])
            ->where(['Login.id' => $clinicianId, 'Login.role' => 'clinician'])
            ->innerJoin(['cu' => 'clinic_users'], 'cu.login_id = Login.id')
            ->andWhere(['cu.clinic_id' => $clinicId])
            ->first();

        return (bool)$ok;
    }

    /** Ensure a participant belongs to the current clinic (if provided). */
    private function assertParticipantInClinic(?int $participantId): bool
    {
        if ($participantId === null) return true;
        $clinicId = $this->currentClinicId();
        if (!$clinicId) return false;

        $ok = $this->Participants->find()
            ->select(['Participants.id'])
            ->where(['Participants.id' => $participantId])
            ->innerJoin(['pc' => 'participants_clinics'], 'pc.participant_id = Participants.id')
            ->andWhere(['pc.clinic_id' => $clinicId])
            ->first();

        return (bool)$ok;
    }

    /* -------------------------- Util helpers ----------------------------- */

    private function readJson(): array
    {
        $raw  = (string)$this->request->getBody();
        $data = json_decode($raw, true);
        return is_array($data) ? $data : (array)$this->request->getData();
    }

    private function wantsJson(): bool
    {
        return ($this->request->getParam('_ext') === 'json')
            || $this->request->accepts('application/json');
    }

    private function nextPosition(?int $clinicianId): int
    {
        // Position remains per-clinician (existing behavior).
        $q = $this->Waitlist->find()
            ->select(['mx' => $this->Waitlist->find()->func()->max('position')]);
        $clinicianId === null
            ? $q->where(['clinician_id IS' => null])
            : $q->where(['clinician_id' => $clinicianId]);
        $row = $q->first();
        $mx  = (int)($row->mx ?? 0);
        return $mx + 1;
    }

    private function pickPhone(?string $contact, ?int $participantId): string
    {
        $c = trim((string)($contact ?? ''));
        if ($c !== '' && preg_match('/\d{6,}/', $c)) return $c;
        if ($participantId) {
            try {
                $p = $this->Participants->get($participantId);
                $ph = trim((string)($p->phone ?? ''));
                if ($ph !== '') return $ph;
            } catch (\Throwable $e) {}
        }
        return '';
    }

    private function notifyOnAdded($e): void
    {
        $phone = $this->pickPhone($e->contact ?? '', $e->participant_id ?: null);
        if ($phone !== '') {
            $this->sendSms($phone, 'You have been added to the waiting list. We will notify you when a slot opens.');
        }
        Log::info('[WL] Added id='.$e->id.' name='.$e->participant_name);
    }

    private function notifyOnMoved($e, $eventId = null): void
    {
        $phone = $this->pickPhone($e->contact ?? '', $e->participant_id ?: null);
        if ($phone !== '') {
            $this->sendSms($phone, 'A slot is available and your appointment has been scheduled.');
        }
        $this->notifyAdminsAndClinician((int)($e->clinician_id ?? 0), 'Waitlist entry scheduled (WL#'.$e->id.'). Event#'.(string)$eventId);
        Log::info('[WL] Scheduled id='.$e->id.' event='.$eventId);
    }

    private function sendSms(string $to, string $text): void
    {
        Log::info('[SMS] to='.$to.' text='.$text);
    }

    private function notifyAdminsAndClinician(int $clinicianId, string $message): void
    {
        Log::info('[NOTIFY] clinician='.$clinicianId.' msg='.$message);
    }

    /* ------------------------------ Actions ------------------------------ */

    public function index()
    {
        $q       = (string)$this->request->getQuery('q', '');
        $status  = (string)$this->request->getQuery('status', '');
        $cid     = (string)$this->request->getQuery('cid', '');
        $perPage = (int)($this->request->getQuery('limit') ?? 20);

        $query = $this->Waitlist->find()
            ->contain(['Participants','Login'])
            ->order([
                'FIELD(Waitlist.status, "waiting","scheduled","cancelled")' => 'ASC',
                'COALESCE(Waitlist.position, 999999)' => 'ASC',
                'Waitlist.created' => 'ASC',
                'Waitlist.id'      => 'ASC',
            ]);

        $this->scopeClinic($query);

        if ($q !== '') {
            $like = '%' . trim($q) . '%';
            $query->where([
                'OR' => [
                    'Waitlist.participant_name LIKE' => $like,
                    'Waitlist.contact LIKE'          => $like,
                    'Participants.first_name LIKE'   => $like,
                    'Participants.last_name LIKE'    => $like,
                    'Participants.email LIKE'        => $like,
                    'Participants.phone LIKE'        => $like,
                ]
            ]);
        }
        if ($status !== '') { $query->where(['Waitlist.status' => $status]); }
        if ($cid !== '')     { $query->where(['Waitlist.clinician_id' => (int)$cid]); }

        // Position repair (scoped by clinician)
        if ($this->needsRepair(null)) { $this->ensurePositions(null); }
        $allClinIds = $this->Login->find()
            ->select(['Login.id'])
            ->innerJoin(['cu' => 'clinic_users'], 'cu.login_id = Login.id')
            ->where(['cu.clinic_id' => $this->currentClinicId(), 'Login.role' => 'clinician'])
            ->all()->extract('id')->toList();
        foreach ($allClinIds as $cidFix) {
            if ($this->needsRepair((int)$cidFix)) { $this->ensurePositions((int)$cidFix); }
        }

        $items = $this->paginate($query, ['limit' => $perPage]);

        // Clinician dropdown: only clinicians in current clinic
        $logins = $this->Login->find()
            ->select(['Login.id','Login.first_name','Login.last_name','Login.email'])
            ->innerJoin(['cu' => 'clinic_users'], 'cu.login_id = Login.id')
            ->where(['cu.clinic_id' => $this->currentClinicId(), 'Login.role' => 'clinician'])
            ->orderAsc('Login.first_name')
            ->all();

        $clinicians = [];
        foreach ($logins as $u) {
            $clinicians[$u->id] = trim(($u->first_name ?? '') . ' ' . ($u->last_name ?? '')) ?: $u->email;
        }

        $this->set(compact('items','q','status','cid','perPage','clinicians'));
    }

    public function feed(): Response
    {
        $this->request->allowMethod(['get']);
        $cid = $this->request->getQuery('clinician_id');
        $q   = $this->request->getQuery('q');

        $find = $this->Waitlist->find()
            ->contain(['Participants','Login'])
            ->where(['Waitlist.status' => 'waiting'])
            ->orderAsc('position')
            ->orderAsc('Waitlist.id');

        $this->scopeClinic($find);

        if ($cid !== null && $cid !== '') { $find->where(['Waitlist.clinician_id' => (int)$cid]); }
        if ($q) {
            $like = '%' . trim((string)$q) . '%';
            $find->where([
                'OR' => [
                    'Waitlist.participant_name LIKE' => $like,
                    'Participants.first_name LIKE'   => $like,
                    'Participants.last_name LIKE'    => $like,
                ]
            ]);
        }

        $out = [];
        foreach ($find as $r) {
            $name = $r->participant_name
                ?: trim(($r->participant?->first_name ?? '') . ' ' . ($r->participant?->last_name ?? ''));
            $clin = trim(($r->login?->first_name ?? '') . ' ' . ($r->login?->last_name ?? ''));
            $out[] = [
                'id'           => (int)$r->id,
                'name'         => $name ?: '—',
                'contact'      => (string)($r->contact ?? ''),
                'clinician'    => $clin ?: '',
                'clinician_id' => $r->clinician_id,
                'desired_date' => $r->desired_date?->i18nFormat('yyyy-MM-dd'),
                'notes'        => (string)$r->notes,
                'position'     => (int)$r->position,
                'status'       => (string)($r->status ?? 'waiting'),
            ];
        }

        return $this->response->withType('json')->withStringBody(json_encode($out));
    }

    public function add()
    {
        $methods = $this->request->is('get') ? ['get','post'] : ['post'];
        $this->request->allowMethod($methods);

        if ($this->request->is('get')) {
            return $this->redirect(['action'=>'index']);
        }

        $d = $this->readJson();

        $participantId = ($d['participant_id'] ?? '') !== '' ? (int)$d['participant_id'] : null;
        $clinicianId   = ($d['clinician_id']   ?? '') !== '' ? (int)$d['clinician_id']   : null;

        // Validate membership in current clinic
        if (!$this->assertParticipantInClinic($participantId)) {
            return $this->response->withStatus(403)->withType('json')
                ->withStringBody(json_encode(['error' => 'Participant is not part of the current clinic']));
        }
        if (!$this->assertClinicianInClinic($clinicianId)) {
            return $this->response->withStatus(403)->withType('json')
                ->withStringBody(json_encode(['error' => 'Clinician is not part of the current clinic']));
        }

        $e = $this->Waitlist->newEmptyEntity();
        $e->participant_id   = $participantId;
        $e->participant_name = (string)($d['participant_name'] ?? '');
        $e->contact          = (string)($d['contact'] ?? '');
        $e->clinician_id     = $clinicianId;
        $e->desired_date     = !empty($d['desired_date']) ? new FrozenDate($d['desired_date']) : null;
        $e->notes            = (string)($d['notes'] ?? '');
        $e->status           = 'waiting';
        $e->priority         = (int)($d['priority'] ?? 0);
        $e->position         = $this->nextPosition($e->clinician_id);

        if (!$this->Waitlist->save($e)) {
            if ($this->wantsJson()) {
                return $this->response->withStatus(422)->withType('json')
                    ->withStringBody(json_encode(['error' => 'Add failed', 'errors' => $e->getErrors()]));
            }
            $this->Flash->error('Add failed.');
            return $this->redirect(['action' => 'index']);
        }

        $this->notifyOnAdded($e);

        if ($this->wantsJson()) {
            return $this->response->withType('json')
                ->withStringBody(json_encode(['ok' => true, 'id' => (int)$e->id]));
        }
        $this->Flash->success('Added to waiting list.');
        return $this->redirect(['action' => 'index']);
    }

    public function move(int $id, string $dir): Response
    {
        $cur = $this->loadScoped($id);
        $cid = $cur->clinician_id;
        $pos = (int)($cur->position ?? 0);

        if ($pos <= 0 || $this->needsRepair($cid)) {
            $this->ensurePositions($cid);
            $cur = $this->loadScoped($id);
            $pos = (int)($cur->position ?? 0);
        }

        $this->request->allowMethod(['post']);
        $dir = strtolower($dir);
        if (!in_array($dir, ['up','down'], true)) {
            return $this->response->withStatus(400)->withType('json')->withStringBody(json_encode(['error'=>'Bad direction']));
        }

        $cmp   = $dir === 'up' ? '<' : '>';
        $order = $dir === 'up' ? ['position'=>'DESC','id'=>'DESC'] : ['position'=>'ASC','id'=>'ASC'];

        $q = $this->Waitlist->find()->select(['Waitlist.id','Waitlist.position'])
            ->where(['Waitlist.status'=>'waiting'])
            ->order($order);
        $this->scopeClinic($q);
        $cid === null ? $q->where(['Waitlist.clinician_id IS' => null]) : $q->where(['Waitlist.clinician_id' => $cid]);
        $q->andWhere(["position {$cmp}" => $pos]);

        $other = $q->first();
        if (!$other) {
            return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
        }

        $tmp = (int)$other->position;
        $otherEnt = $this->Waitlist->get((int)$other->id);
        $cur->position     = $tmp;
        $otherEnt->position = $pos;

        $this->Waitlist->save($cur);
        $this->Waitlist->save($otherEnt);

        return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
    }

    public function reorder(): Response
    {
        $this->request->allowMethod(['post']);
        $d = $this->readJson();
        $ids = array_values(array_filter(array_map('intval', (array)($d['ids'] ?? []))));
        if (!$ids) {
            return $this->response->withStatus(400)->withType('json')->withStringBody(json_encode(['error'=>'No ids']));
        }

        // Ensure the first record is visible to the clinic and use its clinician_id as the bucket
        $firstRow = $this->loadScoped($ids[0]);
        $cid   = $firstRow->clinician_id;

        $i = 1;
        foreach ($ids as $wid) {
            // Only touch rows we can see and that share the same clinician_id bucket
            $e = $this->loadScoped((int)$wid);
            if (($e->clinician_id ?? null) !== ($cid ?? null)) continue;
            $e->position = $i++;
            $this->Waitlist->save($e);
        }
        return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
    }

    public function autoFill(): Response
    {
        $this->request->allowMethod(['post']);
        $d = $this->readJson();
        $userId = (int)($d['user_id'] ?? 0);
        $start  = (string)($d['start'] ?? '');
        $end    = (string)($d['end'] ?? '');

        if (!$userId || !$start || !$end) {
            return $this->response->withStatus(400)->withType('json')->withStringBody(json_encode(['error'=>'Missing params']));
        }

        // Ensure clinician is in this clinic
        if (!$this->assertClinicianInClinic($userId)) {
            return $this->response->withStatus(403)->withType('json')
                ->withStringBody(json_encode(['error'=>'Clinician is not part of the current clinic']));
        }

        $next = $this->Waitlist->find()->contain(['Participants'])
            ->where(['Waitlist.status'=>'waiting','Waitlist.clinician_id'=>$userId])
            ->orderAsc('Waitlist.position')->orderAsc('Waitlist.id');

        $this->scopeClinic($next);

        $next = $next->first();

        if (!$next) {
            return $this->response->withStatus(204)->withType('json')->withStringBody(json_encode(['ok'=>true,'message'=>'No candidate']));
        }

        $Events = $this->fetchTable('CalendarEvents');
        $ev = $Events->newEmptyEntity();
        $title = trim((string)$next->participant_name);
        if ($title === '' && $next->participant) {
            $title = trim(($next->participant->first_name ?? '') . ' ' . ($next->participant->last_name ?? ''));
        }
        $ev->title           = $title !== '' ? $title : 'Appointment';
        $ev->start           = new FrozenTime($start);
        $ev->end             = new FrozenTime($end);
        $ev->user_id         = $userId;
        $ev->status          = 'pending';
        $ev->appointment_type= 'standard';
        $ev->location        = '';
        $ev->description     = (string)($next->notes ?? '');

        if (!$Events->save($ev)) {
            return $this->response->withStatus(422)->withType('json')->withStringBody(json_encode(['error'=>'Create event failed']));
        }

        $rec = $this->Waitlist->get($next->id);
        $rec->status = 'scheduled';
        $this->Waitlist->save($rec);

        $this->notifyOnMoved($rec, (int)$ev->id);

        return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true,'event_id'=>(int)$ev->id]));
    }

    public function markScheduled(int $id)
    {
        $this->request->allowMethod(['post']);
        $e = $this->loadScoped($id);
        $e->status = 'scheduled';

        if (!$this->Waitlist->save($e)) {
            if ($this->wantsJson()) {
                return $this->response->withStatus(422)->withType('json')->withStringBody(json_encode(['error'=>'Update failed']));
            }
            $this->Flash->error('Update failed.');
            return $this->redirect(['action' => 'index']);
        }

        $this->notifyOnMoved($e, null);

        if ($this->wantsJson()) {
            return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
        }
        $this->Flash->success('Marked as scheduled.');
        return $this->redirect(['action' => 'index']);
    }

    public function delete(int $id)
    {
        $this->request->allowMethod(['post']);

        // Ensure scoped visibility before deleting
        $e = $this->loadScoped($id);

        if (!$this->Waitlist->delete($e)) {
            if ($this->wantsJson()) {
                return $this->response->withStatus(422)->withType('json')->withStringBody(json_encode(['error'=>'Delete failed']));
            }
            $this->Flash->error('Delete failed.');
            return $this->redirect(['action' => 'index']);
        }

        if ($this->wantsJson()) {
            return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
        }
        $this->Flash->success('Deleted.');
        return $this->redirect(['action' => 'index']);
    }

    private function ensurePositions(?int $clinicianId): void
    {
        $q = $this->Waitlist->find()
            ->select(['Waitlist.id','Waitlist.position','Waitlist.created'])
            ->where(['Waitlist.status' => 'waiting'])
            ->orderAsc('Waitlist.position')
            ->orderAsc('Waitlist.created')
            ->orderAsc('Waitlist.id');

        $this->scopeClinic($q);

        $clinicianId === null
            ? $q->where(['Waitlist.clinician_id IS' => null])
            : $q->where(['Waitlist.clinician_id' => $clinicianId]);

        $i = 1;
        foreach ($q as $r) {
            if ((int)$r->position !== $i || (int)$r->position <= 0) {
                $e = $this->Waitlist->get((int)$r->id);
                $e->position = $i;
                $this->Waitlist->save($e);
            }
            $i++;
        }
    }

    private function needsRepair(?int $clinicianId): bool
    {
        $q = $this->Waitlist->find()->select(['Waitlist.position'])->where(['Waitlist.status' => 'waiting']);
        $this->scopeClinic($q);

        $clinicianId === null
            ? $q->where(['Waitlist.clinician_id IS' => null])
            : $q->where(['Waitlist.clinician_id' => $clinicianId]);

        $q->andWhere(['OR' => [['Waitlist.position <=' => 0], ['Waitlist.position IS' => null]]]);
        return (bool)$q->first();
    }

    public function edit(int $id)
    {
        $e = $this->loadScoped($id);

        if ($this->request->is('get')) {
            $name = $e->participant_name
                ?: trim(($e->participant->first_name ?? '') . ' ' . ($e->participant->last_name ?? ''));
            $clin = trim(($e->login->first_name ?? '') . ' ' . ($e->login->last_name ?? ''));
            $payload = [
                'id'           => (int)$e->id,
                'name'         => $name ?: '—',
                'contact'      => (string)($e->contact ?? ''),
                'clinician'    => $clin ?: '',
                'clinician_id' => $e->clinician_id,
                'desired_date' => $e->desired_date?->i18nFormat('yyyy-MM-dd'),
                'notes'        => (string)($e->notes ?? ''),
                'status'       => (string)($e->status ?? 'waiting'),
            ];
            return $this->response->withType('json')->withStringBody(json_encode($payload));
        }

        $this->request->allowMethod(['post','patch','put']);
        $d = $this->readJson();

        if (array_key_exists('notes', $d)) {
            $e->notes = (string)$d['notes'];
        }
        if (array_key_exists('clinician_id', $d)) {
            $newCid = ($d['clinician_id'] === '' || $d['clinician_id'] === null) ? null : (int)$d['clinician_id'];
            if (!$this->assertClinicianInClinic($newCid)) {
                return $this->response->withStatus(403)->withType('json')
                    ->withStringBody(json_encode(['error'=>'Clinician is not part of the current clinic']));
            }
            $e->clinician_id = $newCid;
        }
        if (array_key_exists('desired_date', $d)) {
            $dd = (string)$d['desired_date'];
            $e->desired_date = $dd !== '' ? new FrozenDate($dd) : null;
        }
        if (array_key_exists('contact', $d)) {
            $e->contact = (string)$d['contact'];
        }
        if (array_key_exists('participant_name', $d)) {
            $e->participant_name = (string)$d['participant_name'];
        }

        if (!$this->Waitlist->save($e)) {
            return $this->response->withStatus(422)->withType('json')
                ->withStringBody(json_encode(['ok'=>false,'error'=>'Save failed','errors'=>$e->getErrors()]));
        }
        return $this->response->withType('json')->withStringBody(json_encode(['ok'=>true]));
    }
}
