<?php

namespace App\Http\Controllers\Care;

use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Http\Controllers\Controller;
use App\Models\Assessment;
use App\Models\AssessmentTemplate;
use App\Models\Consultation;
use App\Models\Patient;
use App\Models\Visit;
use App\Services\Care\AssessmentService;
use App\Services\Care\CareFeatures;
use App\Services\Care\CarePermissions;
use App\Services\Care\ConsultationReturnContext;
use App\Services\Care\FhirAssessmentExporter;
use App\Services\Care\OrganizationResolver;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AssessmentController extends Controller
{
    use ScopesToAccount;

    public function __construct(
        protected AssessmentService $assessments,
        protected CareFeatures $features,
        protected CarePermissions $permissions,
        protected FhirAssessmentExporter $fhir,
    ) {}

    public function index(Request $request, Patient $patient): View
    {
        $this->assertEngineEnabled($request);
        $this->authorizeAbility($request, 'assessments.view');
        $this->authorizePatient($request, $patient);

        $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
        $assessments = $this->assessments->listForPatient(
            $patient,
            $this->ownerRef($request),
            $request->only(['status', 'template_code', 'category', 'from', 'to', 'per_page']),
            $branchScope,
        );

        $canCapture = $this->canCapture($request);

        return view('care.assessments.index', [
            'patient' => $patient,
            'assessments' => $assessments,
            'canCapture' => $canCapture,
            'statuses' => config('care.assessment_statuses'),
            'categories' => config('care.assessment_template_categories'),
            'filters' => $request->only(['status', 'template_code', 'category']),
            'returnConsultation' => app(ConsultationReturnContext::class)->resolve($request, (int) $patient->id),
        ]);
    }

    /**
     * Consultation-scoped assessments list (same patient forms UI with return context).
     * Supports GET /consultations/{uuid}/assessments — previously only POST existed.
     */
    public function indexForConsultation(Request $request, Consultation $consultation): View
    {
        $this->authorizeConsultation($request, $consultation);
        $consultation->loadMissing('patient');

        $return = app(ConsultationReturnContext::class);
        $return->remember($consultation);
        $request->query->set(ConsultationReturnContext::QUERY_KEY, $consultation->uuid);

        // Visit branch may differ from the patient's home branch; consultation
        // auth already enforced visit-branch scope (same as prescriptions).
        $request->attributes->set('care.assessments.skip_patient_branch', true);

        return $this->index($request, $consultation->patient);
    }

    /**
     * Start-form picker under a consultation (parity with prescriptions create).
     * GET /consultations/{uuid}/assessments/create
     */
    public function createForConsultation(Request $request, Consultation $consultation): View
    {
        $this->authorizeConsultation($request, $consultation);
        $consultation->loadMissing(['patient', 'visit']);

        $return = app(ConsultationReturnContext::class);
        $return->remember($consultation);
        $request->query->set('consultation_uuid', $consultation->uuid);
        if ($consultation->visit?->uuid) {
            $request->query->set('visit_uuid', $consultation->visit->uuid);
        }
        $request->query->set(ConsultationReturnContext::QUERY_KEY, $consultation->uuid);

        $request->attributes->set('care.assessments.skip_patient_branch', true);

        return $this->create($request, $consultation->patient);
    }

    /**
     * Open an assessment while staying on the consultation URL tree.
     * GET /consultations/{uuid}/assessments/{assessment}
     */
    public function showForConsultation(Request $request, Consultation $consultation, Assessment $assessment): View
    {
        $this->authorizeConsultation($request, $consultation);
        abort_unless((int) $assessment->patient_id === (int) $consultation->patient_id, 404);

        $return = app(ConsultationReturnContext::class);
        $return->remember($consultation);
        $request->query->set(ConsultationReturnContext::QUERY_KEY, $consultation->uuid);

        return $this->show($request, $assessment);
    }

    public function create(Request $request, Patient $patient): View
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizePatient($request, $patient);

        $templates = AssessmentTemplate::query()
            ->system()
            ->current()
            ->orderBy('category')
            ->orderBy('name')
            ->get();

        $member = $this->member($request);
        $capturable = $templates->filter(function (AssessmentTemplate $template) use ($member) {
            try {
                $this->assessments->assertCaptureAllowed($member, $template);

                return true;
            } catch (\Throwable) {
                return false;
            }
        })->values();

        $returnConsultation = app(ConsultationReturnContext::class)
            ->resolve($request, (int) $patient->id);
        $returnConsultation?->loadMissing('visit');

        return view('care.assessments.create', [
            'patient' => $patient,
            'templates' => $capturable,
            'categories' => config('care.assessment_template_categories'),
            'consultationUuid' => $request->query('consultation_uuid') ?? $returnConsultation?->uuid,
            'visitUuid' => $request->query('visit_uuid') ?? $returnConsultation?->visit?->uuid,
            'returnConsultation' => $returnConsultation,
        ]);
    }
    public function store(Request $request, Patient $patient): RedirectResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizePatient($request, $patient);

        $validated = $request->validate([
            'template_code' => ['required', 'string', 'max:100'],
            'consultation_uuid' => ['nullable', 'uuid'],
            'visit_uuid' => ['nullable', 'uuid'],
        ]);

        $context = $this->resolveContext($request, $patient, $validated);
        $assessment = $this->assessments->start(
            $patient,
            $validated['template_code'],
            $this->ownerRef($request),
            $this->member($request),
            $context,
        );

        $return = app(ConsultationReturnContext::class);
        if (! empty($context['consultation'])) {
            $return->remember($context['consultation']);

            return redirect()
                ->route('care.consultations.assessments.show', [
                    'consultation' => $context['consultation'],
                    'assessment' => $assessment,
                ])
                ->with('success', 'Assessment started.');
        }

        return redirect()
            ->route('care.assessments.show', array_filter([
                'assessment' => $assessment,
                ...$return->routeQuery($request),
            ]))
            ->with('success', 'Assessment started.');
    }

    public function storeForConsultation(Request $request, Consultation $consultation): RedirectResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizeConsultation($request, $consultation);

        $validated = $request->validate([
            'template_code' => ['required', 'string', 'max:100'],
        ]);

        $consultation->loadMissing(['patient', 'visit']);
        // Org/owner only — branch was checked via authorizeConsultation (visit).
        $request->attributes->set('care.assessments.skip_patient_branch', true);
        $this->authorizePatient($request, $consultation->patient);

        $assessment = $this->assessments->start(
            $consultation->patient,
            $validated['template_code'],
            $this->ownerRef($request),
            $this->member($request),
            [
                'consultation' => $consultation,
                'visit' => $consultation->visit,
                'actor' => $this->ownerRef($request),
            ],
        );

        $return = app(ConsultationReturnContext::class);
        $return->remember($consultation);

        return redirect()
            ->route('care.consultations.assessments.show', [
                'consultation' => $consultation,
                'assessment' => $assessment,
            ])
            ->with('success', 'Assessment started.');
    }

    public function show(Request $request, Assessment $assessment): View
    {
        $this->assertEngineEnabled($request);
        $this->authorizeAbility($request, 'assessments.view');
        $this->authorizeAssessment($request, $assessment);

        $assessment->load([
            'template.questions',
            'answers.question',
            'patient',
            'consultation',
            'visit',
            'practitioner',
            'score',
        ]);

        $answersByCode = $assessment->answers
            ->mapWithKeys(fn ($answer) => [
                $answer->question->code => $answer->authoritativeValue($answer->question->answer_type),
            ]);

        $canEdit = false;
        if ($assessment->isDraft() && $this->canCapture($request)) {
            try {
                $this->assessments->assertCaptureAllowed($this->member($request), $assessment->template);
                $canEdit = true;
            } catch (\Throwable) {
                $canEdit = false;
            }
        }

        $return = app(ConsultationReturnContext::class);

        return view('care.assessments.show', [
            'assessment' => $assessment,
            'answersByCode' => $answersByCode,
            'canEdit' => $canEdit,
            'statuses' => config('care.assessment_statuses'),
            'returnConsultation' => $return->resolve($request, (int) $assessment->patient_id)
                ?? $return->resolveLinked($request, $assessment->consultation),
        ]);
    }

    public function update(Request $request, Assessment $assessment): RedirectResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizeAssessment($request, $assessment);

        $validated = $request->validate([
            'answers' => ['nullable', 'array'],
            'notes' => ['nullable', 'string', 'max:10000'],
        ]);

        $this->assessments->saveAnswers(
            $assessment,
            $this->ownerRef($request),
            $validated['answers'] ?? [],
            $this->member($request),
            $this->ownerRef($request),
            array_key_exists('notes', $validated) ? $validated['notes'] : null,
        );

        $return = app(ConsultationReturnContext::class);

        return $this->redirectToAssessmentShow($request, $assessment, $return)
            ->with('success', 'Assessment saved.');
    }

    public function complete(Request $request, Assessment $assessment): RedirectResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizeAssessment($request, $assessment);

        // Persist any posted answers first (complete form can include them).
        if ($request->has('answers') || $request->has('notes')) {
            $validated = $request->validate([
                'answers' => ['nullable', 'array'],
                'notes' => ['nullable', 'string', 'max:10000'],
            ]);
            $this->assessments->saveAnswers(
                $assessment,
                $this->ownerRef($request),
                $validated['answers'] ?? [],
                $this->member($request),
                $this->ownerRef($request),
                array_key_exists('notes', $validated) ? $validated['notes'] : null,
            );
            $assessment->refresh();
        }

        $this->assessments->complete(
            $assessment,
            $this->ownerRef($request),
            $this->member($request),
            $this->ownerRef($request),
        );

        $return = app(ConsultationReturnContext::class);

        return $this->redirectToAssessmentShow($request, $assessment, $return)
            ->with('success', 'Assessment completed.');
    }

    public function cancel(Request $request, Assessment $assessment): RedirectResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeCapture($request);
        $this->authorizeAssessment($request, $assessment);

        $this->assessments->cancel(
            $assessment,
            $this->ownerRef($request),
            $this->member($request),
            $this->ownerRef($request),
        );

        $return = app(ConsultationReturnContext::class);
        $consultation = $return->resolve($request, (int) $assessment->patient_id);

        if ($consultation) {
            return redirect()
                ->route('care.consultations.assessments.index', $consultation)
                ->with('success', 'Assessment cancelled.');
        }

        return redirect()
            ->route('care.assessments.index', $assessment->patient)
            ->with('success', 'Assessment cancelled.');
    }

    /**
     * Prefer consultation-nested show when the doctor is mid-consult.
     */
    protected function redirectToAssessmentShow(
        Request $request,
        Assessment $assessment,
        ConsultationReturnContext $return,
    ): RedirectResponse {
        $consultation = $return->resolve($request, (int) $assessment->patient_id)
            ?? $return->resolveLinked($request, $assessment->consultation);

        if ($consultation) {
            return redirect()->route('care.consultations.assessments.show', [
                'consultation' => $consultation,
                'assessment' => $assessment,
            ]);
        }

        return redirect()->route('care.assessments.show', $assessment);
    }

    /**
     * FHIR R4 Bundle (Questionnaire + QuestionnaireResponse) download.
     */
    public function fhirExport(Request $request, Assessment $assessment): StreamedResponse|JsonResponse
    {
        $this->assertEngineEnabled($request);
        $this->authorizeAbility($request, 'assessments.view');
        $this->authorizeAssessment($request, $assessment);

        $bundle = $this->fhir->exportBundle($assessment);
        $filename = 'assessment-'.$assessment->uuid.'-fhir.json';

        if ($request->wantsJson() && ! $request->boolean('download')) {
            return response()->json($bundle);
        }

        return response()->streamDownload(
            function () use ($bundle) {
                echo json_encode($bundle, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
            },
            $filename,
            ['Content-Type' => 'application/fhir+json'],
        );
    }

    protected function assertEngineEnabled(Request $request): void
    {
        abort_unless(
            $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE),
            404,
        );
        $this->features->ensureAssessmentCatalog();
    }

    protected function authorizeCapture(Request $request): void
    {
        abort_unless($this->canCapture($request), 403);
    }

    protected function canCapture(Request $request): bool
    {
        $member = $this->member($request);

        return $this->permissions->can($member, 'assessments.capture')
            || $this->permissions->can($member, 'assessments.manage');
    }

    protected function authorizePatient(Request $request, Patient $patient): void
    {
        $this->authorizeOwner($request, $patient);
        abort_unless($patient->organization_id === $this->organization($request)->id, 404);

        if ($request->attributes->get('care.assessments.skip_patient_branch')) {
            return;
        }

        $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
        if ($branchId !== null && $patient->branch_id !== $branchId) {
            abort(404);
        }
    }

    protected function authorizeConsultation(Request $request, Consultation $consultation): void
    {
        $this->authorizeOwner($request, $consultation);
        $consultation->loadMissing('visit');
        abort_unless($consultation->visit->organization_id === $this->organization($request)->id, 404);

        $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
        if ($branchId !== null && $consultation->visit->branch_id !== $branchId) {
            abort(404);
        }
    }

    protected function authorizeAssessment(Request $request, Assessment $assessment): void
    {
        $this->authorizeOwner($request, $assessment);
        abort_unless($assessment->organization_id === $this->organization($request)->id, 404);

        $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
        if ($branchId !== null && $assessment->branch_id !== $branchId) {
            abort(404);
        }
    }

    /**
     * @param  array{consultation_uuid?: ?string, visit_uuid?: ?string}  $validated
     * @return array{consultation?: Consultation, visit?: Visit, actor: string}
     */
    protected function resolveContext(Request $request, Patient $patient, array $validated): array
    {
        $context = ['actor' => $this->ownerRef($request)];

        if (! empty($validated['consultation_uuid'])) {
            $consultation = Consultation::query()
                ->where('uuid', $validated['consultation_uuid'])
                ->where('patient_id', $patient->id)
                ->firstOrFail();
            $this->authorizeConsultation($request, $consultation);
            $consultation->loadMissing('visit');
            $context['consultation'] = $consultation;
            $context['visit'] = $consultation->visit;
        } elseif (! empty($validated['visit_uuid'])) {
            $visit = Visit::query()
                ->where('uuid', $validated['visit_uuid'])
                ->where('patient_id', $patient->id)
                ->firstOrFail();
            $this->authorizeOwner($request, $visit);
            abort_unless($visit->organization_id === $this->organization($request)->id, 404);
            $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
            if ($branchId !== null && $visit->branch_id !== $branchId) {
                abort(404);
            }
            $context['visit'] = $visit;
        }

        return $context;
    }
}
