<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\EvaluationCriterion;
use App\Models\EvaluationScore;
use App\Models\EvaluationTemplate;
use App\Models\MonthlyEvaluation;
use App\Models\Student;
use App\Models\TemplateAssignment;
use Illuminate\Http\Request;

/**
 * The evaluator's working surface (design §07, §08). A period always opens
 * empty; previous months are returned only as a read-only reference. Submission
 * runs the smart-validation gate and snapshots criteria so history is frozen.
 */
class MonthlyEvaluationController extends Controller
{
    private array $adminRoles = ['super-admin', 'admin', 'manager', 'principal'];

    /** Classes + templates this evaluator may work on. */
    public function assignable(Request $request)
    {
        $me = $request->user();
        $branchId = $request->header('X-Branch-Id') ?: $request->query('branch_id');
        $isAdmin = $me && in_array($me->role, $this->adminRoles, true);

        $q = TemplateAssignment::query()
            ->join('evaluation_templates as t', 't.id', '=', 'template_assignments.template_id')
            ->join('classes as c', 'c.id', '=', 'template_assignments.class_id')
            ->where('t.status', 'active')
            ->when($branchId, fn ($qq) => $qq->where('t.branch_id', $branchId))
            ->when(! $isAdmin && $me, fn ($qq) => $qq->where('template_assignments.evaluator_id', $me->id))
            ->select('template_assignments.template_id', 'template_assignments.class_id',
                't.name as template_name', 'c.name as class_name')
            ->get();

        return response()->json(['data' => $q, 'is_admin' => $isAdmin]);
    }

    /** Open (or create) the draft for a class+template+period and return everything the grid needs. */
    public function open(Request $request)
    {
        $me = $request->user();
        $data = $request->validate([
            'template_id' => 'required|integer',
            'class_id' => 'required|integer',
            'period_year' => 'required|integer',
            'period_month' => 'required|integer',
        ]);

        $template = EvaluationTemplate::with('criteria')->findOrFail($data['template_id']);
        $criteria = $template->criteria;

        $eval = MonthlyEvaluation::firstOrCreate(
            [
                'class_id' => $data['class_id'],
                'evaluator_id' => $me?->id,
                'period_year' => $data['period_year'],
                'period_month' => $data['period_month'],
            ],
            [
                'branch_id' => $request->header('X-Branch-Id') ?: null,
                'template_id' => $template->id,
                'status' => 'draft',
            ]
        );

        $students = Student::where('status', 'active')->where('class_id', $data['class_id'])
            ->orderByRaw('created_at is null, created_at')  // new students last
            ->get(['id', 'first_name', 'last_name', 'photo', 'gender', 'admission_number', 'created_at']);

        // Current values (empty on a fresh period).
        $scores = $eval->scores()->get()->groupBy('student_id')->map(
            fn ($g) => $g->keyBy('criterion_id')->map(fn ($s) => ['score' => $s->score, 'rating' => $s->rating, 'comment' => $s->comment])
        );

        // Read-only reference: the most recent PRIOR period for this class+template.
        $prior = MonthlyEvaluation::where('class_id', $data['class_id'])
            ->where('template_id', $template->id)
            ->where(fn ($q) => $q->where('period_year', '<', $data['period_year'])
                ->orWhere(fn ($w) => $w->where('period_year', $data['period_year'])->where('period_month', '<', $data['period_month'])))
            ->orderByDesc('period_year')->orderByDesc('period_month')->first();
        $reference = [];
        if ($prior) {
            $reference = EvaluationScore::where('monthly_evaluation_id', $prior->id)->get()
                ->groupBy('student_id')->map(fn ($g) => $g->keyBy('criterion_id')->map(fn ($s) => $s->score));
        }

        return response()->json([
            'evaluation' => $eval,
            'template' => ['id' => $template->id, 'name' => $template->name, 'version' => $template->version],
            'criteria' => $criteria,
            'students' => $students->map(fn ($s) => [
                'id' => $s->id, 'name' => trim("$s->first_name $s->last_name"),
                'photo' => $s->photo, 'gender' => $s->gender, 'admission_number' => $s->admission_number,
                'is_new' => $s->created_at && $s->created_at->gt(now()->subMonths(2)),
            ]),
            'scores' => $scores,
            'reference' => $reference,
            'reference_period' => $prior ? ['year' => $prior->period_year, 'month' => $prior->period_month] : null,
        ]);
    }

    /** Autosave / bulk save the current draft. */
    public function saveScores(Request $request, $id)
    {
        $eval = MonthlyEvaluation::findOrFail($id);
        abort_unless(in_array($eval->status, ['draft', 'correction']), 422, 'This evaluation is locked.');
        $rows = $request->validate([
            'scores' => 'present|array',
            'scores.*.student_id' => 'required|integer',
            'scores.*.criterion_id' => 'required|integer',
            'scores.*.score' => 'nullable|numeric',
            'scores.*.rating' => 'nullable|string',
            'scores.*.comment' => 'nullable|string',
        ])['scores'];

        foreach ($rows as $r) {
            $existing = EvaluationScore::where('monthly_evaluation_id', $eval->id)
                ->where('student_id', $r['student_id'])->where('criterion_id', $r['criterion_id'])->first();
            $changed = ! $existing || (string) $existing->score !== (string) ($r['score'] ?? null);
            EvaluationScore::updateOrCreate(
                ['monthly_evaluation_id' => $eval->id, 'student_id' => $r['student_id'], 'criterion_id' => $r['criterion_id']],
                [
                    'score' => $r['score'] ?? null,
                    'rating' => $r['rating'] ?? null,
                    'comment' => $r['comment'] ?? null,
                    'edited_count' => ($existing->edited_count ?? 0) + ($changed ? 1 : 0),
                ]
            );
        }

        return response()->json(['ok' => true, 'saved' => count($rows)]);
    }

    /** Smart-validation gate — the list of scores that still need a comment. */
    public function validateGate($id)
    {
        $eval = MonthlyEvaluation::findOrFail($id);
        $unmet = $this->computeTriggers($eval);

        return response()->json(['unmet' => $unmet, 'ok' => count($unmet) === 0]);
    }

    /** Validate then submit: snapshot criteria, freeze, set status. */
    public function submit(Request $request, $id)
    {
        $eval = MonthlyEvaluation::with('scores')->findOrFail($id);
        abort_unless(in_array($eval->status, ['draft', 'correction']), 422, 'Already submitted.');

        $unmet = $this->computeTriggers($eval);
        if (count($unmet) > 0) {
            return response()->json(['ok' => false, 'unmet' => $unmet], 422);
        }

        $template = EvaluationTemplate::with('criteria')->find($eval->template_id);
        $critById = $template ? $template->criteria->keyBy('id') : collect();

        // Snapshot each criterion onto its score rows + copy comments with a
        // similarity hash for the repeated-comment detector.
        foreach ($eval->scores as $s) {
            $crit = $critById->get($s->criterion_id);
            if ($crit) {
                $s->criterion_snapshot = $crit->snapshot();
                $s->save();
            }
            if ($s->comment) {
                \App\Models\EvaluationComment::create([
                    'score_id' => $s->id,
                    'body' => $s->comment,
                    'similarity_hash' => sha1(preg_replace('/\s+/', ' ', mb_strtolower(trim($s->comment)))),
                ]);
            }
        }

        // Weighted class average (0–100) from snapshot weights.
        $eval->class_avg = $this->classAverage($eval, $template);
        $eval->template_snapshot = $template ? [
            'id' => $template->id, 'name' => $template->name, 'version' => $template->version,
            'criteria' => $template->criteria->map->snapshot()->values(),
        ] : null;
        $eval->status = 'submitted';
        $eval->submitted_at = now();
        $eval->save();

        // Passive integrity pass — session, detectors, confidence (design §09–§11).
        $integrity = app(\App\Services\IntegrityService::class)->analyze($eval->fresh('scores'));

        // Fast-track: if review isn't required (policy, no flags, high confidence)
        // the evaluation is auto-approved (design §12). Otherwise it awaits review.
        $policy = \App\Support\SystemConfig::get('eval_review_policy', 'smart'); // smart / always
        $hasFlags = \App\Models\ReviewFlag::where('monthly_evaluation_id', $eval->id)->where('status', 'open')->exists();
        $needsReview = $policy === 'always' || $hasFlags || ($integrity['confidence'] ?? 0) < 80;
        if (! $needsReview) {
            $eval->update(['status' => 'approved']);
        }
        $integrity['needs_review'] = $needsReview;

        return response()->json(['ok' => true, 'evaluation' => $eval->fresh(), 'integrity' => $integrity]);
    }

    public function index(Request $request)
    {
        $me = $request->user();
        $branchId = $request->header('X-Branch-Id') ?: $request->query('branch_id');
        $isAdmin = $me && in_array($me->role, $this->adminRoles, true);
        $rows = MonthlyEvaluation::with(['schoolClass:id,name', 'evaluator:id,name'])
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->when(! $isAdmin && $me, fn ($q) => $q->where('evaluator_id', $me->id))
            ->when($request->query('status'), fn ($q, $v) => $q->where('status', $v))
            ->orderByDesc('period_year')->orderByDesc('period_month')->orderByDesc('updated_at')->get();

        return response()->json(['data' => $rows]);
    }

    /** Printable monthly evaluation report for one student (design §15). */
    public function studentReport($id, $studentId)
    {
        $eval = MonthlyEvaluation::with(['schoolClass:id,name', 'evaluator:id,name'])->findOrFail($id);
        $student = Student::findOrFail($studentId);
        $criteria = collect($eval->template_snapshot['criteria'] ?? [])
            ->keyBy('id')
            ->union(EvaluationCriterion::where('template_id', $eval->template_id)->get()->map->snapshot()->keyBy('id'));

        $scores = EvaluationScore::where('monthly_evaluation_id', $eval->id)->where('student_id', $studentId)->get()->keyBy('criterion_id');
        $lines = [];
        $sum = 0;
        $w = 0;
        foreach ($criteria as $cid => $c) {
            $s = $scores->get($cid);
            $max = ($c['max_score'] ?? 10) ?: 10;
            $pct = $s && $s->score !== null ? round($s->score / $max * 100, 1) : null;
            if ($pct !== null) {
                $sum += $pct * ($c['weight'] ?? 0);
                $w += $c['weight'] ?? 0;
            }
            $lines[] = [
                'criterion' => $c['name'] ?? '—',
                'weight' => $c['weight'] ?? 0,
                'max' => $max,
                'score' => $s->score ?? null,
                'pct' => $pct,
                'comment' => $s->comment ?? null,
            ];
        }
        $total = $w > 0 ? round($sum / $w, 1) : null;

        return response()->json(['data' => [
            'student' => ['id' => $student->id, 'name' => trim("$student->first_name $student->last_name"),
                'photo' => $student->photo, 'gender' => $student->gender,
                'admission_number' => $student->admission_number, 'father_name' => $student->father_name],
            'class_name' => $eval->schoolClass?->name,
            'evaluator' => $eval->evaluator?->name,
            'period' => ['year' => $eval->period_year, 'month' => $eval->period_month],
            'status' => $eval->status,
            'lines' => $lines,
            'total' => $total,
            'grade' => $this->letterGrade($total),
            'confidence' => \App\Models\ConfidenceScore::where('monthly_evaluation_id', $eval->id)->value('score'),
            'school_name' => \App\Support\SystemConfig::get('school_name', 'Shams Tabriz School'),
            'issued_on' => now()->toDateString(),
        ]]);
    }

    private function letterGrade(?float $t): ?string
    {
        if ($t === null) {
            return null;
        }

        return match (true) {
            $t >= 90 => 'A+', $t >= 80 => 'A', $t >= 70 => 'B', $t >= 60 => 'C', $t >= 50 => 'D', default => 'F',
        };
    }

    // ── helpers ──

    private function computeTriggers(MonthlyEvaluation $eval): array
    {
        $criteria = EvaluationCriterion::where('template_id', $eval->template_id)->get()->keyBy('id');
        $prior = MonthlyEvaluation::where('class_id', $eval->class_id)->where('template_id', $eval->template_id)
            ->where('id', '!=', $eval->id)
            ->where(fn ($q) => $q->where('period_year', '<', $eval->period_year)
                ->orWhere(fn ($w) => $w->where('period_year', $eval->period_year)->where('period_month', '<', $eval->period_month)))
            ->orderByDesc('period_year')->orderByDesc('period_month')->first();
        $prev = $prior ? EvaluationScore::where('monthly_evaluation_id', $prior->id)->get()
            ->groupBy('student_id')->map(fn ($g) => $g->keyBy('criterion_id')) : collect();

        $unmet = [];
        foreach ($eval->scores()->get() as $s) {
            if ($s->score === null) {
                continue;
            }
            $crit = $criteria->get($s->criterion_id);
            if (! $crit) {
                continue;
            }
            $needs = false;
            $reason = null;
            $max = $crit->max_score ?: 100;
            switch ($crit->comment_rule) {
                case 'required':
                    $needs = true;
                    $reason = 'required';
                    break;
                case 'on_extreme':
                    if ($s->score <= 0.2 * $max || $s->score >= $max) {
                        $needs = true;
                        $reason = 'extreme';
                    }
                    break;
                case 'on_low':
                    if ($s->score < ($crit->rule_threshold ?? 0.5 * $max)) {
                        $needs = true;
                        $reason = 'low';
                    }
                    break;
                case 'on_change':
                    $p = $prev->get($s->student_id)?->get($s->criterion_id)?->score;
                    if ($p !== null && abs($s->score - $p) >= ($crit->rule_threshold ?? 3)) {
                        $needs = true;
                        $reason = 'change';
                    }
                    break;
            }
            if ($needs && ! trim((string) $s->comment)) {
                $unmet[] = [
                    'student_id' => $s->student_id,
                    'criterion_id' => $s->criterion_id,
                    'criterion' => $crit->name,
                    'reason' => $reason,
                ];
            }
        }

        return $unmet;
    }

    private function classAverage(MonthlyEvaluation $eval, ?EvaluationTemplate $template): ?float
    {
        if (! $template) {
            return null;
        }
        $weights = $template->criteria->keyBy('id');
        $byStudent = $eval->scores->groupBy('student_id');
        if ($byStudent->isEmpty()) {
            return null;
        }
        $totals = $byStudent->map(function ($g) use ($weights) {
            $sum = 0;
            $w = 0;
            foreach ($g as $s) {
                $c = $weights->get($s->criterion_id);
                if (! $c || $s->score === null) {
                    continue;
                }
                $max = $c->max_score ?: 100;
                $sum += ($s->score / $max * 100) * ($c->weight ?: 0);
                $w += $c->weight ?: 0;
            }

            return $w > 0 ? $sum / $w : null;
        })->filter();

        return $totals->isEmpty() ? null : round($totals->avg(), 2);
    }
}
