<?php

namespace App\Services\Care;

use App\Contracts\Care\ScoresAssessment;
use App\Models\Assessment;
use App\Models\AssessmentQuestion;
use App\Models\AssessmentScore;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use InvalidArgumentException;

class ScoringService
{
    /**
     * Preview scores without persisting.
     *
     * @return array{total: ?float, max: ?float, subscores: array<string, float|int>, severity_label: ?string}
     */
    public function preview(Assessment $assessment): array
    {
        $assessment->loadMissing(['template.questions', 'answers.question']);

        return $this->compute($assessment);
    }

    public function materialize(Assessment $assessment): AssessmentScore
    {
        $assessment->loadMissing(['template.questions', 'answers.question']);

        try {
            $result = $this->compute($assessment);
        } catch (\Throwable $e) {
            Log::warning('care.scoring.failure', [
                'template_code' => $assessment->template?->code,
                'assessment_uuid' => $assessment->uuid,
                'message' => $e->getMessage(),
            ]);
            throw $e;
        }

        return AssessmentScore::updateOrCreate(
            ['assessment_id' => $assessment->id],
            [
                'owner_ref' => $assessment->owner_ref,
                'template_code' => $assessment->template->code,
                'total_score' => $result['total'],
                'max_score' => $result['max'],
                'subscores' => $result['subscores'] !== [] ? $result['subscores'] : null,
                'severity_label' => $result['severity_label'],
                'computed_at' => now(),
            ],
        );
    }

    /**
     * Whether this template requires score materialization on complete.
     */
    public function requiresScore(Assessment $assessment): bool
    {
        $strategy = $assessment->template?->scoring_strategy;

        return filled($strategy);
    }

    /**
     * @return array{total: ?float, max: ?float, subscores: array<string, float|int>, severity_label: ?string}
     */
    protected function compute(Assessment $assessment): array
    {
        $strategy = $assessment->template?->scoring_strategy;

        if (! filled($strategy)) {
            return [
                'total' => null,
                'max' => null,
                'subscores' => [],
                'severity_label' => null,
            ];
        }

        if (str_starts_with($strategy, 'custom:')) {
            $result = $this->customScore($assessment, substr($strategy, 7));
        } else {
            $result = match ($strategy) {
                'sum_items' => $this->sumItems($assessment),
                'single_value' => $this->singleValue($assessment),
                default => throw ValidationException::withMessages([
                    'scoring' => "Unknown scoring strategy [{$strategy}].",
                ]),
            };
        }

        $result['severity_label'] = $result['severity_label']
            ?? $this->severityFromBands($assessment->template?->meta, $result['total'] ?? null);

        return $result;
    }

    /**
     * @param  array<string, mixed>|null  $meta
     */
    protected function severityFromBands(?array $meta, mixed $total): ?string
    {
        if ($total === null || ! is_numeric($total)) {
            return null;
        }

        $bands = $meta['severity_bands'] ?? null;
        if (! is_array($bands) || $bands === []) {
            return null;
        }

        $score = (float) $total;
        foreach ($bands as $band) {
            if (! is_array($band) || ! isset($band['max'], $band['label'])) {
                continue;
            }
            if ($score <= (float) $band['max']) {
                return (string) $band['label'];
            }
        }

        return null;
    }

    /**
     * @return array{total: ?float, max: ?float, subscores: array<string, float|int>, severity_label: ?string}
     */
    protected function sumItems(Assessment $assessment): array
    {
        $scoreItems = $assessment->template->questions
            ->where('answer_type', AssessmentQuestion::TYPE_SCORE_ITEM);

        if ($scoreItems->isEmpty()) {
            throw ValidationException::withMessages([
                'scoring' => 'sum_items strategy requires score_item questions.',
            ]);
        }

        $answersByQuestion = $assessment->answers->keyBy('question_id');
        $total = 0.0;
        $max = 0.0;
        $subscores = [];
        $missing = [];

        foreach ($scoreItems as $question) {
            $answer = $answersByQuestion->get($question->id);
            $value = $answer?->value_number;

            if ($value === null || $value === '') {
                $missing[] = $question->code;
                continue;
            }

            $num = (float) $value;
            $total += $num;
            $itemMax = $this->itemMaxScore($question);
            $max += $itemMax;

            $key = $question->score_key ?: $question->code;
            $subscores[$key] = ($subscores[$key] ?? 0) + $num;
        }

        if ($missing !== []) {
            throw ValidationException::withMessages(
                collect($missing)->mapWithKeys(
                    fn ($code) => ["answers.{$code}" => 'Score item is required for scoring.']
                )->all()
            );
        }

        return [
            'total' => $total,
            'max' => $max > 0 ? $max : null,
            'subscores' => $subscores,
            'severity_label' => null,
        ];
    }

    /**
     * @return array{total: ?float, max: ?float, subscores: array<string, float|int>, severity_label: ?string}
     */
    protected function singleValue(Assessment $assessment): array
    {
        $scored = $assessment->template->questions->filter(
            fn (AssessmentQuestion $q) => in_array($q->answer_type, [
                AssessmentQuestion::TYPE_SCORE_ITEM,
                AssessmentQuestion::TYPE_SCALE,
                AssessmentQuestion::TYPE_NUMBER,
            ], true)
        );

        if ($scored->isEmpty()) {
            throw ValidationException::withMessages([
                'scoring' => 'single_value strategy requires a scored question.',
            ]);
        }

        $question = $scored->sortBy('sort_order')->first();
        $answer = $assessment->answers->firstWhere('question_id', $question->id);
        if ($answer?->value_number === null || $answer->value_number === '') {
            throw ValidationException::withMessages([
                "answers.{$question->code}" => 'Score is required for scoring.',
            ]);
        }

        $total = (float) $answer->value_number;
        $max = $this->itemMaxScore($question);

        return [
            'total' => $total,
            'max' => $max > 0 ? $max : null,
            'subscores' => [$question->score_key ?: $question->code => $total],
            'severity_label' => null,
        ];
    }

    /**
     * @return array{total: ?float, max: ?float, subscores: array<string, float|int>, severity_label: ?string}
     */
    protected function customScore(Assessment $assessment, string $handlerName): array
    {
        if (! preg_match('/^[A-Za-z][A-Za-z0-9_]*$/', $handlerName)) {
            throw new InvalidArgumentException('Invalid custom scoring handler name.');
        }

        $class = 'App\\Services\\Care\\Scoring\\'.$handlerName;
        if (! class_exists($class)) {
            throw ValidationException::withMessages([
                'scoring' => "Scoring handler [{$handlerName}] is not registered.",
            ]);
        }

        $handler = app($class);
        if (! $handler instanceof ScoresAssessment) {
            throw new InvalidArgumentException("Handler [{$handlerName}] must implement ScoresAssessment.");
        }

        $result = $handler->score($assessment);

        return [
            'total' => isset($result['total']) ? (float) $result['total'] : null,
            'max' => isset($result['max']) ? (float) $result['max'] : null,
            'subscores' => is_array($result['subscores'] ?? null) ? $result['subscores'] : [],
            'severity_label' => $result['severity_label'] ?? null,
        ];
    }

    protected function itemMaxScore(AssessmentQuestion $question): float
    {
        $options = $question->options ?? [];
        if (isset($options['max']) && is_numeric($options['max'])) {
            return (float) $options['max'];
        }

        $choices = $options['choices'] ?? [];
        $scores = [];
        foreach ($choices as $choice) {
            if (isset($choice['score']) && is_numeric($choice['score'])) {
                $scores[] = (float) $choice['score'];
            }
        }

        return $scores !== [] ? max($scores) : 0.0;
    }
}
