<?php

namespace Modules\VetAI\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\VetAI\Entities\VaiSetting;
use Modules\VetAI\Services\VaiTranscriptionService;

/**
 * Voice-to-text endpoints used by the SOAP "🎤" buttons on the appointment
 * consultation tab. Both routes are gated by vet_ai.use_transcription.
 *
 *  POST /vet-ai/transcribe      — multipart audio → plain transcript JSON
 *  POST /vet-ai/soap-structure  — transcript → {subjective, objective, assessment, plan}
 */
class VaiTranscriptionController extends Controller
{
    private function guard(): void
    {
        if (!auth()->user()->can('vet_ai.use_transcription')) {
            abort(403, 'Unauthorized action.');
        }
    }

    public function transcribe(Request $request)
    {
        $this->guard();

        $request->validate([
            'audio'    => 'required|file|max:25600|mimes:webm,ogg,mp3,wav,m4a,mp4',
            'language' => 'nullable|string|max:10',
            // Section-specific vocabulary hint - typically the visible chip
            // labels for the textarea the doctor is dictating into. Forwarded
            // verbatim to Whisper's `prompt` parameter to bias decoding.
            'prompt'   => 'nullable|string|max:1000',
        ]);

        $businessId = $request->session()->get('user.business_id');
        $setting = VaiSetting::forBusiness($businessId);

        if (!$setting->isTranscriptionConfigured()) {
            return response()->json([
                'success' => false,
                'msg'     => 'Voice transcription is not configured. Open VetAI Settings → Voice transcription.',
            ], 422);
        }

        // Web Speech runs in the browser — server should never be asked to transcribe.
        $cfg = $setting->transcriptionConfig();
        if ($cfg['provider'] === 'webspeech') {
            return response()->json([
                'success' => false,
                'msg'     => 'This clinic is configured for browser-only Web Speech — the client should send the recognised text, not the audio.',
            ], 422);
        }

        try {
            $result = VaiTranscriptionService::transcribe(
                $setting,
                $request->file('audio'),
                $request->input('language'),
                $request->input('prompt')
            );
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'msg' => 'Transcription failed: ' . $e->getMessage()], 502);
        }

        // Optional AI post-processing: strip non-medical side-talk so the
        // textarea only gets the doctor's clinical dictation. Falls back
        // to the raw transcript if AI is unconfigured or errors. Per-business
        // toggle from VetAI Settings; default ON.
        $raw = $result['text'] ?? '';
        $cleaned = $raw;
        $cleanNote = null;
        if ($setting->t_clean_non_medical && $setting->isConfigured() && trim($raw) !== '') {
            $cleanRes  = VaiTranscriptionService::cleanMedicalContent($setting, $raw);
            $cleaned   = $cleanRes['cleaned'];
            $cleanNote = $cleanRes['note'] ?? null;
        }

        return response()->json(array_merge(
            ['success' => true],
            $result,
            [
                'text'        => $cleaned,
                'raw_text'    => $raw,          // available for "show original" if UI ever wants it
                'cleaned'     => $cleaned !== $raw,
                'clean_note'  => $cleanNote,
            ]
        ));
    }

    public function structureSoap(Request $request)
    {
        $this->guard();

        $request->validate([
            'transcript' => 'required|string|max:20000',
            'context'    => 'nullable|string|max:2000',
        ]);

        $businessId = $request->session()->get('user.business_id');
        $setting = VaiSetting::forBusiness($businessId);

        if (!$setting->isConfigured()) {
            // No text-LLM configured — return the transcript verbatim as the
            // Subjective so the doctor still gets their dictation.
            return response()->json([
                'success' => true,
                'subjective' => $request->input('transcript'),
                'objective' => '', 'assessment' => '', 'plan' => '',
                'note' => 'No text AI configured — auto-structure skipped.',
            ]);
        }

        $structured = VaiTranscriptionService::structureSoap(
            $setting, $request->input('transcript'), $request->input('context')
        );
        return response()->json(array_merge(['success' => true], $structured));
    }

    /**
     * Synonym-aware chip matcher used by the SOAP "complaint chips" panel.
     * The frontend posts the current Subjective/Lab/Exam textarea text and
     * the list of chip labels visible on the page; we return the subset the
     * LLM judges to apply (catching synonyms like "threw up" -> "Vomiting").
     *
     * Gated by the existing transcription permission since it's part of the
     * same dictation UX. Fails soft: if no text-LLM is configured the route
     * returns an empty match list with success=true so the JS doesn't show
     * an error and just falls back to its literal substring matcher.
     */
    /**
     * Extract structured clinical findings from free-text Subjective.
     * Returns labelled symptoms with severity flags so the UI can show
     * a "Key clinical findings" pills row beneath the textarea.
     */
    public function extractSymptoms(Request $request)
    {
        if (!auth()->user()->can('vet_ai.use_assistant')
            && !auth()->user()->can('vet_clinic.manage_appointments')) {
            abort(403, 'Not authorised.');
        }
        $request->validate([
            'subjective' => 'required|string|max:5000',
        ]);
        $businessId = $request->session()->get('user.business_id');
        $setting = VaiSetting::forBusiness($businessId);
        if (!$setting->isConfigured()) {
            return response()->json([
                'success'  => true,
                'findings' => [],
                'overall_concern' => 'low',
                'note'     => 'No text AI configured.',
            ]);
        }
        $result = VaiTranscriptionService::extractSymptoms(
            $setting, $request->input('subjective')
        );
        return response()->json(array_merge(['success' => true], $result));
    }

    /**
     * Item #3 — Save the currently-displayed AI advice to the appointment
     * row so the doctor doesn't pay for the same analysis on next reload.
     * Idempotent: called by the frontend after each successful AI fetch
     * AND when the doctor explicitly accepts a suggestion.
     */
    public function cacheAdvice(Request $request)
    {
        if (!auth()->user()->can('vet_clinic.manage_appointments')) {
            abort(403, 'Not authorised.');
        }
        $request->validate([
            'appointment_id'   => 'required|integer',
            'subjective'       => 'required|string|max:5000',
            'exam_suggestions' => 'nullable',
            'symptoms'         => 'nullable',
        ]);
        $businessId = $request->session()->get('user.business_id');
        $appt = \Modules\VetClinic\Entities\VcAppointment::where('business_id', $businessId)
            ->findOrFail($request->appointment_id);
        if (!\Schema::hasColumn('vc_appointments', 'ai_subjective_hash')) {
            return response()->json(['success' => false, 'msg' => 'AI cache migration not applied.'], 500);
        }
        $appt->forceFill([
            'ai_subjective_hash'        => sha1(trim($request->subjective)),
            'ai_exam_suggestions_json'  => $request->exam_suggestions,
            'ai_symptoms_json'          => $request->symptoms,
            'ai_advice_updated_at'      => now(),
        ])->save();
        return response()->json(['success' => true, 'cached_at' => $appt->ai_advice_updated_at?->toIso8601String()]);
    }

    /**
     * Item #4b — propose specialist exam panels for the Objective tab
     * based on the current Subjective text.
     *
     * Permission: vet_ai.use_assistant OR vet_clinic.record_exam_findings
     * (anyone who can fill in exam findings benefits from the suggestion).
     */
    public function suggestSpecialistExams(Request $request)
    {
        if (!auth()->user()->can('vet_ai.use_assistant')
            && !auth()->user()->can('vet_clinic.record_exam_findings')
            && !auth()->user()->can('vet_clinic.manage_appointments')) {
            abort(403, 'Not authorised to use AI exam suggestions.');
        }

        $request->validate([
            'subjective'           => 'required|string|max:5000',
            'available_exams'      => 'required|array|min:1|max:30',
            'available_exams.*'    => 'string|max:60',
        ]);

        $businessId = $request->session()->get('user.business_id');
        $setting = VaiSetting::forBusiness($businessId);

        if (!$setting->isConfigured()) {
            return response()->json([
                'success'   => true,
                'suggested' => [],
                'note'      => 'No text AI configured.',
            ]);
        }

        $result = VaiTranscriptionService::suggestSpecialistExams(
            $setting,
            $request->input('subjective'),
            $request->input('available_exams', [])
        );
        return response()->json(array_merge(['success' => true], $result));
    }

    public function matchChips(Request $request)
    {
        $this->guard();

        $request->validate([
            'text'      => 'required|string|max:5000',
            'labels'    => 'required|array|min:1|max:200',
            'labels.*'  => 'string|max:120',
            // Optional: pull the full DB of this section's chips and let the
            // LLM pick from the wider pool too, so terms not currently on the
            // visible chip row (e.g. "Anorexia" when only "Not eating" is up)
            // can still be auto-added.
            'section'   => 'nullable|string|in:complaint,lab_test,exam_test',
            'species'   => 'nullable|string|max:30',
        ]);

        $businessId = $request->session()->get('user.business_id');
        $setting = VaiSetting::forBusiness($businessId);

        if (!$setting->isConfigured()) {
            return response()->json([
                'success' => true,
                'matched' => [],
                'note'    => 'No text AI configured - chip mapping skipped.',
            ]);
        }

        // Build the candidate label pool: start with the visible labels
        // supplied by the frontend, then add the rest of the section's DB
        // (active only, scoped to this business + species). De-dup; cap at
        // 200 so the LLM prompt stays well within budget.
        $labels = array_values(array_unique(array_filter(array_map('strval', $request->input('labels', [])), 'strlen')));

        if ($request->filled('section')) {
            $section = $request->input('section');
            $species = $request->input('species') ?: 'all';
            $dbLabels = \Modules\VetClinic\Entities\VcSoapOption::active()
                ->where('section', $section)
                ->where(function ($q) use ($businessId) {
                    $q->whereNull('business_id')->orWhere('business_id', $businessId);
                })
                ->where(function ($q) use ($species) {
                    $q->where('species', 'all')->orWhere('species', $species);
                })
                ->pluck('label')
                ->map(fn($l) => (string) $l)
                ->filter()
                ->unique()
                ->values()
                ->all();
            $labels = array_values(array_unique(array_merge($labels, $dbLabels)));
        }

        if (count($labels) > 200) {
            $labels = array_slice($labels, 0, 200);
        }

        $result = VaiTranscriptionService::matchChips(
            $setting,
            $request->input('text'),
            $labels
        );
        return response()->json(array_merge(['success' => true], $result));
    }
}
