<?php

namespace App\Services;

use App\Models\ConfidenceScore;
use App\Models\EvaluationComment;
use App\Models\EvaluationScore;
use App\Models\EvaluationSession;
use App\Models\MonthlyEvaluation;
use App\Models\ReviewFlag;
use App\Support\SystemConfig;

/**
 * The passive integrity engine (design §09–§11). Runs once at submission:
 * records the session, runs eight detectors that can only raise
 * "Review recommended", and produces a transparent confidence score.
 *
 * Nothing here ever blocks a submission, changes a score, or judges an
 * evaluator — it only observes, scores and flags for a human to consider.
 */
class IntegrityService
{
    /** Configurable thresholds with safe defaults. */
    private function cfg(string $key, $default)
    {
        return SystemConfig::get("integrity_$key", $default);
    }

    public function analyze(MonthlyEvaluation $eval): array
    {
        $scores = EvaluationScore::where('monthly_evaluation_id', $eval->id)->get();
        $byStudent = $scores->groupBy('student_id');
        $studentsN = $byStudent->count();

        // ── Session ──
        $start = $scores->min('created_at') ?: $eval->created_at;
        $end = $eval->submitted_at ?: now();
        $duration = max(0, $end->getTimestamp() - \Illuminate\Support\Carbon::parse($start)->getTimestamp());
        $avgPer = $studentsN ? round($duration / $studentsN, 2) : null;
        $totalEdits = (int) $scores->sum('edited_count');

        EvaluationSession::updateOrCreate(
            ['monthly_evaluation_id' => $eval->id],
            ['started_at' => $start, 'submitted_at' => $end, 'duration_s' => $duration,
                'students_n' => $studentsN, 'avg_per_student_s' => $avgPer, 'total_edits' => $totalEdits]
        );

        // Clear prior flags for a clean re-evaluation on re-submit.
        ReviewFlag::where('monthly_evaluation_id', $eval->id)->where('status', 'open')->delete();
        $flags = [];
        $add = function ($type, $severity, $evidence) use (&$flags, $eval) {
            $flags[] = ReviewFlag::create([
                'monthly_evaluation_id' => $eval->id, 'branch_id' => $eval->branch_id,
                'type' => $type, 'severity' => $severity, 'evidence' => $evidence, 'status' => 'open',
            ]);
        };

        // ── 1. Extremely fast completion ──
        $floor = (float) $this->cfg('fast_floor_s', 5);
        if ($studentsN >= 10 && $avgPer !== null && $avgPer < $floor) {
            $add('fast', 'warning', ['avg_per_student_s' => $avgPer, 'floor' => $floor, 'students' => $studentsN, 'duration_s' => $duration]);
        }

        // ── 2. Identical scores (full vector) ──
        $vectors = $byStudent->map(fn ($g) => $g->sortBy('criterion_id')->pluck('score')->implode(','));
        $identicalShare = $studentsN ? $vectors->countBy()->max() / $studentsN : 0;
        if ($studentsN >= 8 && $identicalShare > (float) $this->cfg('identical_share', 0.7)) {
            $add('identical', 'danger', ['share' => round($identicalShare * 100), 'students' => $studentsN]);
        }

        // ── 3. Very low variation (per criterion) ──
        $byCrit = $scores->groupBy('criterion_id');
        foreach ($byCrit as $cid => $g) {
            $vals = $g->pluck('score')->filter(fn ($v) => $v !== null)->values();
            if ($vals->count() < 8) {
                continue;
            }
            $modeShare = $vals->countBy()->max() / $vals->count();
            if ($modeShare >= (float) $this->cfg('low_var_share', 0.95)) {
                $add('low_variance', 'warning', ['criterion_id' => $cid, 'same_value_share' => round($modeShare * 100), 'value' => $vals->countBy()->sortDesc()->keys()->first()]);
                break;
            }
        }

        // ── 4. Extreme peer difference ──
        $peer = MonthlyEvaluation::where('period_year', $eval->period_year)->where('period_month', $eval->period_month)
            ->where('id', '!=', $eval->id)->whereNotNull('class_avg')
            ->when($eval->branch_id, fn ($q) => $q->where('branch_id', $eval->branch_id))
            ->avg('class_avg');
        if ($peer !== null && $eval->class_avg !== null && abs($eval->class_avg - $peer) > (float) $this->cfg('peer_gap', 30)) {
            $add('peer', 'warning', ['evaluator_avg' => round($eval->class_avg, 1), 'peer_avg' => round($peer, 1), 'gap' => round(abs($eval->class_avg - $peer), 1)]);
        }

        // ── 5. Sudden class shift ──
        $priorAvgs = MonthlyEvaluation::where('class_id', $eval->class_id)->where('template_id', $eval->template_id)
            ->where('id', '!=', $eval->id)->whereNotNull('class_avg')
            ->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')->limit(3)->pluck('class_avg');
        if ($priorAvgs->count() && $eval->class_avg !== null) {
            $priorMean = $priorAvgs->avg();
            if (abs($eval->class_avg - $priorMean) > (float) $this->cfg('shift_gap', 25)) {
                $add('shift', 'warning', ['now' => round($eval->class_avg, 1), 'prior_avg' => round($priorMean, 1), 'gap' => round(abs($eval->class_avg - $priorMean), 1)]);
            }
        }

        // ── 6. Repeated comments ──
        $hashes = EvaluationComment::whereIn('score_id', $scores->pluck('id'))->pluck('similarity_hash')->filter();
        if ($hashes->count() >= 5) {
            $repShare = $hashes->countBy()->max() / $hashes->count();
            if ($repShare > (float) $this->cfg('repeat_share', 0.6)) {
                $add('repeated', 'info', ['repeated_share' => round($repShare * 100), 'total_comments' => $hashes->count()]);
            }
        }

        // ── 7. Missing comments (coverage of scores that NEEDED one) ──
        // Coverage is measured only against scores that triggered a comment
        // rule — an evaluation that legitimately needs no comments is fully
        // covered, not penalised.
        $criteria = \App\Models\EvaluationCriterion::where('template_id', $eval->template_id)->get()->keyBy('id');
        $triggered = 0;
        $covered = 0;
        foreach ($scores as $s) {
            $c = $criteria->get($s->criterion_id);
            if (! $c || $s->score === null) {
                continue;
            }
            $max = $c->max_score ?: 100;
            $needs = match ($c->comment_rule) {
                'required' => true,
                'on_extreme' => $s->score <= 0.2 * $max || $s->score >= $max,
                'on_low' => $s->score < ($c->rule_threshold ?? 0.5 * $max),
                default => false,
            };
            if ($needs) {
                $triggered++;
                if (trim((string) $s->comment) !== '') {
                    $covered++;
                }
            }
        }
        $coverage = $triggered ? $covered / $triggered : 1.0;

        // ── 8. Excessive editing ──
        $maxEdits = (int) $scores->max('edited_count');
        if ($maxEdits > (int) $this->cfg('edit_ceiling', 6)) {
            $add('edits', 'info', ['max_edits' => $maxEdits, 'total_edits' => $totalEdits]);
        }

        // ══════════ CONFIDENCE ══════════
        $factors = $this->confidenceFactors($avgPer, $floor, $identicalShare, $coverage,
            $eval->class_avg, $priorAvgs->count() ? $priorAvgs->avg() : null, $peer);
        $score = (int) round(array_sum(array_map(fn ($f) => $f['value'] * 20, $factors)));
        $band = $score >= 80 ? 'high' : ($score >= 50 ? 'medium' : 'low');

        ConfidenceScore::updateOrCreate(
            ['monthly_evaluation_id' => $eval->id],
            ['score' => $score, 'band' => $band, 'factors' => $factors]
        );

        return ['confidence' => $score, 'band' => $band, 'flags' => count($flags)];
    }

    private function confidenceFactors($avgPer, $floor, $identicalShare, $coverage, $classAvg, $priorAvg, $peerAvg): array
    {
        // Duration reasonableness
        $dur = $avgPer === null ? 1 : ($avgPer < $floor ? 0.2 : ($avgPer < $floor * 2 ? 0.6 : 1));
        // Distribution normality
        $dist = $identicalShare > 0.9 ? 0.15 : ($identicalShare > 0.7 ? 0.5 : ($identicalShare > 0.5 ? 0.8 : 1));
        // Comment coverage
        $cov = max(0, min(1, $coverage * 1.5));
        // Historical consistency
        $hist = $priorAvg === null || $classAvg === null ? 0.9 : max(0, 1 - min(1, abs($classAvg - $priorAvg) / 50));
        // Peer consistency
        $peer = $peerAvg === null || $classAvg === null ? 0.9 : max(0, 1 - min(1, abs($classAvg - $peerAvg) / 50));

        return [
            ['key' => 'duration', 'value' => round($dur, 2)],
            ['key' => 'distribution', 'value' => round($dist, 2)],
            ['key' => 'coverage', 'value' => round($cov, 2)],
            ['key' => 'history', 'value' => round($hist, 2)],
            ['key' => 'peers', 'value' => round($peer, 2)],
        ];
    }
}
