<?php

namespace Modules\PortalApi\Http\Controllers\Api;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Modules\PortalApi\Entities\PortalBrand;
use Modules\PortalApi\Entities\PortalOwner;
use Modules\PortalApi\Support\BookingSlotResolver;
use Modules\PortalApi\Support\PortalDataRepository;
use Modules\PortalApi\Support\PortalFixtures;
use Modules\PortalApi\Support\VerifyToken;
use Modules\VetClinic\Entities\VcPet;

class PortalController extends Controller
{
    /* ─── Brand (public) ───────────────────────────────────── */

    public function brand(Request $request, string $slug): JsonResponse
    {
        $brand = PortalBrand::forSlug($slug) ?? PortalBrand::forRequest($request);
        if ($brand) {
            return response()->json($brand->toPwaPayload());
        }
        return response()->json(PortalFixtures::brand($slug));
    }

    /** No-slug brand resolver — picks the right brand from the request host. */
    public function brandResolve(Request $request): JsonResponse
    {
        $brand = PortalBrand::forRequest($request);
        if ($brand) {
            return response()->json($brand->toPwaPayload());
        }
        return response()->json(PortalFixtures::brand('demo'));
    }

    /* ─── Authenticated endpoints ──────────────────────────── */

    public function me(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json($repo->me());
        }
        return response()->json(PortalFixtures::me());
    }

    public function home(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json($repo->home());
        }
        return response()->json(PortalFixtures::home());
    }

    public function pets(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json($repo->pets());
        }
        return response()->json(PortalFixtures::pets());
    }

    public function pet(Request $request, int $id): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            $pet = $repo->pet($id);
            return $pet ? response()->json($pet) : response()->json(['message' => 'Pet not found'], 404);
        }
        $pet = PortalFixtures::pet($id);
        return $pet ? response()->json($pet) : response()->json(['message' => 'Pet not found'], 404);
    }

    public function vaxCard(Request $request, int $id): JsonResponse
    {
        $clinic    = $request->query('clinic',   'Pet Portal');
        $ownerName = $request->query('owner',    'Pet Owner');
        $sig       = VerifyToken::sign($id);
        $verifyUrl = url('/verify/' . $id . '?sig=' . $sig);

        if ($repo = $this->repo($request)) {
            $card = $repo->vaxCard($id, $verifyUrl, $clinic, $ownerName);
        } else {
            $card = PortalFixtures::vaxCard($id, $verifyUrl, $clinic, $ownerName);
        }

        return $card ? response()->json($card) : response()->json(['message' => 'Pet not found'], 404);
    }

    public function reasons(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            $rows = $repo->reasons();
            if (! empty($rows)) return response()->json($rows);
        }
        // Fallback when the business has no appointment types configured —
        // ship the PWA still works with the generic catalogue.
        return response()->json(PortalFixtures::reasons());
    }

    public function slots(Request $request): JsonResponse
    {
        $date       = $request->query('date', now()->toDateString());
        $petId      = (int) $request->query('pet_id', 0);
        $locationId = $request->query('location_id') ? (int) $request->query('location_id') : null;
        $duration   = (int) $request->query('duration', 30);
        // Owner can override the preferred doctor with an explicit pick
        // (used after they accept an "alternative doctor" suggestion).
        $doctorId   = $request->query('doctor_id') ? (int) $request->query('doctor_id') : null;

        /** @var ?PortalOwner $owner */
        $owner = $request->attributes->get('portal_owner');

        // Fall back to generated grid when no real owner (DEV demo-* token).
        if (! $owner instanceof PortalOwner) {
            return response()->json(['slots' => PortalFixtures::slots($date)]);
        }

        $pet = $petId
            ? VcPet::where('business_id', $owner->business_id)
                ->where('contact_id', $owner->contact_id)
                ->where('id', $petId)
                ->first()
            : null;

        $resolver = new BookingSlotResolver($owner->business_id);
        $resolution = $resolver->resolve(
            pet: $pet,
            locationId: $locationId,
            date: Carbon::parse($date),
            durationMinutes: $duration,
            overrideDoctorId: $doctorId
        );

        return response()->json($resolution);
    }

    /** Public-list of this clinic's physical locations. */
    public function locations(Request $request): JsonResponse
    {
        /** @var ?PortalOwner $owner */
        $owner = $request->attributes->get('portal_owner');
        if (! $owner instanceof PortalOwner) {
            return response()->json([]);
        }
        $rows = DB::table('vc_clinic_locations')
            ->where('business_id', $owner->business_id)
            ->where(function ($q) { $q->where('status', 'active')->orWhere('status', 1); })
            ->orderBy('name')
            ->get(['id','name','address','phone']);
        return response()->json($rows);
    }

    public function appointments(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json($repo->appointments());
        }
        return response()->json(PortalFixtures::appointments());
    }

    /**
     * Active + recent in-clinic stays for this owner's pets — covers
     * hospitalisation, observation, and boarding from the VetAdmission
     * module. Empty arrays when the module isn't installed.
     */
    public function admissions(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json($repo->admissions());
        }
        return response()->json(['active' => [], 'past' => []]);
    }

    /**
     * Owner-initiated cancel. Reschedule from the PWA is handled by
     * cancelling the old appointment + booking a new one through the
     * existing /appointments POST flow — no separate endpoint needed.
     */
    public function cancelAppointment(Request $request, int $id): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            $r = $repo->cancelAppointment($id);
            if (isset($r['error'])) {
                $messages = [
                    'not_found'       => 'Appointment not found or not yours.',
                    'not_cancellable' => 'This appointment can no longer be cancelled.',
                ];
                return response()->json(['message' => $messages[$r['error']] ?? 'Cancel failed.'], 422);
            }
            return response()->json($r);
        }
        // DEV demo-* token: pretend it worked so the design is testable.
        return response()->json(['ok' => true, 'appointment_id' => $id]);
    }

    public function book(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            $result = $repo->book($request->all());
            if (isset($result['error'])) {
                return response()->json(['message' => 'Pet not found or not yours.'], 404);
            }
            return response()->json($result, 201);
        }

        // Fallback (demo-* token in DEV): mocked booking response
        $start = $request->input('slot_start');
        $dt    = $start ? new \DateTime($start) : now();
        $label = $dt->format('D M j') . ' · ' . $dt->format('H:i');
        $pet   = PortalFixtures::pet((int) $request->input('pet_id'));
        return response()->json([
            'appointment_id' => random_int(1000, 9999),
            'ref_no'         => 'APT-' . strtoupper(substr(uniqid(), -6)),
            'date_label'     => $label,
            'pet_name'       => $pet['name'] ?? 'pet',
            'doctor_name'    => 'Dr. Magdy F',
        ], 201);
    }

    /* ─── Owner profile + account ─────────────────────────────── */

    public function updateMe(Request $request): JsonResponse
    {
        $data = $request->validate([
            'name'  => 'sometimes|string|max:191',
            'phone' => 'sometimes|nullable|string|max:32',
            'email' => 'sometimes|nullable|email|max:191',
        ]);
        if ($repo = $this->repo($request)) {
            $r = $repo->updateProfile($data);
            if (isset($r['error'])) return response()->json(['message' => 'Nothing to update.'], 422);
            return response()->json($r);
        }
        return response()->json(['message' => 'Not available in demo mode.'], 503);
    }

    public function deleteAccount(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            $repo->deleteAccount();
            return response()->json(['ok' => true]);
        }
        return response()->json(['message' => 'Not available in demo mode.'], 503);
    }

    public function exportData(Request $request): JsonResponse
    {
        if ($repo = $this->repo($request)) {
            return response()->json([
                'me'             => $repo->me(),
                'pets'           => $repo->pets(),
                'appointments'   => $repo->appointments(),
                'prescriptions'  => $repo->prescriptions(),
                'medical_reports'=> $repo->medicalReports(),
                'invoices'       => $repo->invoices(),
                'messages'       => $repo->messages(),
                'notifications'  => $repo->notifications(),
                'exported_at'    => now()->toIso8601String(),
            ]);
        }
        return response()->json(['message' => 'Not available in demo mode.'], 503);
    }

    /* ─── Pet photo ───────────────────────────────────────────── */

    public function uploadPetPhoto(Request $request, int $id): JsonResponse
    {
        $request->validate(['photo' => 'required|file|image|mimes:png,jpg,jpeg,webp|max:2048']);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['message' => 'Not available in demo mode.'], 503);

        $file = $request->file('photo');
        $ext  = strtolower($file->getClientOriginalExtension() ?: 'jpg');
        $rel  = 'uploads/pet-photos/' . $id;
        $abs  = public_path($rel);
        if (! is_dir($abs)) @mkdir($abs, 0775, true);
        $name = 'p-' . \Illuminate\Support\Str::random(8) . '.' . $ext;
        $file->move($abs, $name);
        $url = url($rel . '/' . $name);

        $r = $repo->setPetPhoto($id, $abs . '/' . $name, $url);
        if (isset($r['error'])) return response()->json(['message' => 'Pet not found.'], 404);
        return response()->json($r);
    }

    /* ─── Weights, symptoms, medical, prescriptions, labs, invoices, msgs ── */

    public function weights(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->weights($id) : ['entries' => [], 'unit' => 'kg', 'current' => 0]);
    }
    public function addWeight(Request $request, int $id): JsonResponse {
        $data = $request->validate(['weight_kg' => 'required|numeric|min:0.1|max:200', 'notes' => 'nullable|string|max:191']);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true, 'id' => 0]);
        $r = $repo->addWeight($id, (float) $data['weight_kg'], $data['notes'] ?? null);
        if (isset($r['error'])) return response()->json(['message' => 'Could not save weight.'], 422);
        return response()->json($r, 201);
    }

    public function symptoms(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->symptoms($id) : ['entries' => []]);
    }
    public function addSymptom(Request $request, int $id): JsonResponse {
        $data = $request->validate([
            'body'     => 'required|string|max:1000',
            'severity' => 'nullable|in:low,moderate,high,urgent',
            'photos'   => 'nullable|array',
            'photos.*' => 'file|image|mimes:png,jpg,jpeg,webp|max:2048',
        ]);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true, 'id' => 0]);
        $urls = [];
        foreach ($request->file('photos', []) as $f) {
            $rel = 'uploads/symptoms/' . $id;
            $abs = public_path($rel);
            if (! is_dir($abs)) @mkdir($abs, 0775, true);
            $name = 's-' . \Illuminate\Support\Str::random(8) . '.' . strtolower($f->getClientOriginalExtension() ?: 'jpg');
            $f->move($abs, $name);
            $urls[] = url($rel . '/' . $name);
        }
        $r = $repo->addSymptom($id, $data['body'], $data['severity'] ?? 'low', $urls);
        if (isset($r['error'])) return response()->json(['message' => 'Pet not found.'], 404);
        return response()->json($r, 201);
    }

    public function medicalReports(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->medicalReports() : []);
    }
    public function medicalReport(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['message' => 'Not available.'], 503);
        $r = $repo->medicalReport($id);
        if (! $r) return response()->json(['message' => 'Not found.'], 404);
        return response()->json($r);
    }
    public function appointmentSoap(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['message' => 'Not available.'], 503);
        $r = $repo->appointmentSoap($id);
        if (! $r) return response()->json(['message' => 'Not found.'], 404);
        return response()->json($r);
    }

    public function prescriptions(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->prescriptions() : []);
    }
    public function requestRefill(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        $r = $repo->requestRefill($id, $request->input('notes'));
        if (isset($r['error'])) return response()->json(['message' => 'Prescription not found.'], 404);
        return response()->json($r);
    }

    public function labSessions(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->labSessions() : []);
    }
    public function labSession(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['message' => 'Not available.'], 503);
        $r = $repo->labSession($id);
        if (! $r) return response()->json(['message' => 'Not found.'], 404);
        return response()->json($r);
    }

    public function invoices(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->invoices() : []);
    }

    public function messages(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->messages() : []);
    }
    public function sendMessage(Request $request): JsonResponse {
        $data = $request->validate(['body' => 'required|string|max:2000']);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        $r = $repo->sendMessage($data['body']);
        if (isset($r['error'])) return response()->json(['message' => 'Empty message.'], 422);
        return response()->json($r, 201);
    }
    public function markMessagesRead(Request $request): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        return response()->json($repo->markMessagesRead());
    }

    public function telehealth(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->telehealthRequests() : []);
    }
    public function submitTelehealth(Request $request): JsonResponse {
        $data = $request->validate([
            'pet_id'   => 'required|integer',
            'question' => 'required|string|max:2000',
            'photos'   => 'nullable|array',
            'photos.*' => 'file|image|mimes:png,jpg,jpeg,webp|max:2048',
        ]);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        $urls = [];
        foreach ($request->file('photos', []) as $f) {
            $rel = 'uploads/telehealth';
            $abs = public_path($rel);
            if (! is_dir($abs)) @mkdir($abs, 0775, true);
            $name = 't-' . \Illuminate\Support\Str::random(8) . '.' . strtolower($f->getClientOriginalExtension() ?: 'jpg');
            $f->move($abs, $name);
            $urls[] = url($rel . '/' . $name);
        }
        $r = $repo->submitTelehealth((int) $data['pet_id'], $data['question'], $urls);
        if (isset($r['error'])) return response()->json(['message' => 'Pet not found.'], 404);
        return response()->json($r, 201);
    }

    public function submitReview(Request $request, int $id): JsonResponse {
        $data = $request->validate(['rating' => 'required|integer|min:1|max:5', 'comments' => 'nullable|string|max:1000']);
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        $r = $repo->submitReview($id, (int) $data['rating'], $data['comments'] ?? null);
        if (isset($r['error'])) {
            $msg = $r['error'] === 'not_reviewable' ? 'This visit is not completed yet.' : 'Visit not found.';
            return response()->json(['message' => $msg], 422);
        }
        return response()->json($r, 201);
    }
    public function reviewFor(Request $request, int $id): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? ($repo->reviewFor($id) ?? new \stdClass) : new \stdClass);
    }

    public function notifications(Request $request): JsonResponse {
        $repo = $this->repo($request);
        return response()->json($repo ? $repo->notifications() : []);
    }
    public function markNotificationsRead(Request $request): JsonResponse {
        $repo = $this->repo($request);
        if (! $repo) return response()->json(['ok' => true]);
        return response()->json($repo->markNotificationsRead());
    }

    public function search(Request $request): JsonResponse {
        $repo = $this->repo($request);
        $q = (string) $request->query('q', '');
        return response()->json($repo ? $repo->search($q) : ['pets' => [], 'appointments' => [], 'prescriptions' => [], 'invoices' => []]);
    }

    /* ─── Calendar (.ics) generator ────────────────────────────── */

    /**
     * Serves a single appointment as an RFC-5545 .ics file. Tapping a "Add
     * to calendar" link in the PWA opens this URL; the OS handles the rest.
     */
    public function appointmentIcs(Request $request, int $id)
    {
        $repo = $this->repo($request);
        if (! $repo) abort(503);
        $soap = $repo->appointmentSoap($id);
        if (! $soap) abort(404);

        $apt = \DB::table('vc_appointments')->where('id', $id)
            ->where('business_id', $request->attributes->get('portal_owner')->business_id)
            ->first(['appointment_date','appointment_time','duration_minutes','ref_no','type']);
        if (! $apt) abort(404);

        // appointment_date is a full datetime in the DB ('2026-06-02 00:00:00');
        // bolt the actual appointment_time onto it via setTimeFromTimeString
        // rather than string concat (which double-times the value).
        $start    = Carbon::parse($apt->appointment_date)->setTimeFromTimeString($apt->appointment_time ?? '00:00:00');
        $end      = (clone $start)->addMinutes((int) ($apt->duration_minutes ?? 30));
        $clinic   = config('app.name', 'Pet Portal');
        $uid      = 'apt-' . $id . '@' . parse_url(url('/'), PHP_URL_HOST);

        $lines = [
            'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//PetPortal//EN', 'CALSCALE:GREGORIAN',
            'BEGIN:VEVENT',
            'UID:' . $uid,
            'DTSTAMP:' . now()->utc()->format('Ymd\THis\Z'),
            'DTSTART:' . $start->utc()->format('Ymd\THis\Z'),
            'DTEND:'   . $end->utc()->format('Ymd\THis\Z'),
            'SUMMARY:' . self::icsEscape(($apt->type ?: 'Visit') . ' · ' . $soap['pet_name']),
            'DESCRIPTION:' . self::icsEscape('Visit ref ' . ($apt->ref_no ?? '') . ' with ' . ($soap['doctor_name'] ?? '')),
            'LOCATION:' . self::icsEscape($clinic),
            'END:VEVENT', 'END:VCALENDAR',
        ];
        return response(implode("\r\n", $lines), 200, [
            'Content-Type'        => 'text/calendar; charset=utf-8',
            'Content-Disposition' => 'attachment; filename="visit-' . $id . '.ics"',
        ]);
    }

    private static function icsEscape(string $v): string
    {
        return str_replace([',', ';', "\n"], ['\,', '\;', '\n'], $v);
    }

    /* ─── PDF generator (vax card / medical report) ───────────── */

    /**
     * Lightweight printable HTML the browser can save as PDF. Avoids
     * pulling in a PDF library — every modern browser has Save-as-PDF
     * built into the print dialog, which works fine for owner-facing docs.
     */
    public function vaxCardPrint(Request $request, int $id)
    {
        $repo = $this->repo($request);
        if (! $repo) abort(503);
        $verifyUrl = url('/portal-api/verify/' . $id);
        $clinic    = config('app.name', 'Pet Portal');
        $owner     = optional($request->attributes->get('portal_owner'))->name ?? '';
        $card      = $repo->vaxCard($id, $verifyUrl, $clinic, $owner);
        if (! $card) abort(404);
        return response()->view('portalApi::print.vax_card', compact('card', 'clinic', 'verifyUrl'));
    }

    public function medicalReportPrint(Request $request, int $id)
    {
        $repo = $this->repo($request);
        if (! $repo) abort(503);
        $report = $repo->medicalReport($id);
        if (! $report) abort(404);
        $clinic = config('app.name', 'Pet Portal');
        return response()->view('portalApi::print.medical_report', compact('report', 'clinic'));
    }

    /* ─── Helper ────────────────────────────────────────────── */

    /**
     * Build a real-data repository when the request has a fully-resolved
     * authenticated owner. Returns null for dev-only "demo-*" tokens, in
     * which case the caller falls back to PortalFixtures.
     */
    private function repo(Request $request): ?PortalDataRepository
    {
        $owner = $request->attributes->get('portal_owner');
        return ($owner instanceof PortalOwner) ? new PortalDataRepository($owner) : null;
    }
}
