<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\ConfidenceScore;
use App\Models\EvaluationCriterion;
use App\Models\EvaluationScore;
use App\Models\EvaluationTemplate;
use App\Models\MonthlyEvaluation;
use App\Models\ReviewFlag;
use App\Models\SchoolClass;
use App\Models\Student;
use App\Models\TemplateAssignment;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * End-to-end coverage of the Monthly Student Evaluation module: the empty-start
 * rule, the smart-validation gate, the integrity engine, and the
 * submit → review → publish workflow. (See docs/EVALUATION_MODULE.md.)
 */
class MonthlyEvaluationTest extends TestCase
{
    use RefreshDatabase;

    private Branch $branch;
    private SchoolClass $class;
    private EvaluationTemplate $template;
    private EvaluationCriterion $discipline;
    private EvaluationCriterion $academic;

    protected function setUp(): void
    {
        parent::setUp();

        $this->branch = Branch::create(['name' => 'Main', 'code' => 'MC', 'is_active' => true]);
        $this->class = SchoolClass::create(['name' => 'Grade 7 - A', 'branch_id' => $this->branch->id, 'level' => 7, 'section' => 'A']);

        for ($i = 1; $i <= 12; $i++) {
            Student::create([
                'branch_id' => $this->branch->id, 'class_id' => $this->class->id,
                'first_name' => 'Student', 'last_name' => (string) $i, 'gender' => $i % 2 ? 'male' : 'female',
                'admission_number' => 'ADM-'.str_pad((string) $i, 3, '0', STR_PAD_LEFT), 'status' => 'active',
            ]);
        }

        $this->template = EvaluationTemplate::create([
            'branch_id' => $this->branch->id, 'name' => 'Conduct', 'status' => 'active', 'academic_year' => '1404',
        ]);
        $this->discipline = EvaluationCriterion::create([
            'template_id' => $this->template->id, 'name' => 'Discipline', 'weight' => 40, 'max_score' => 10,
            'rating_type' => 'numeric', 'comment_rule' => 'on_low', 'rule_threshold' => 4, 'sort_order' => 0,
        ]);
        $this->academic = EvaluationCriterion::create([
            'template_id' => $this->template->id, 'name' => 'Academic', 'weight' => 60, 'max_score' => 100,
            'rating_type' => 'percent', 'comment_rule' => 'optional', 'sort_order' => 1,
        ]);
        TemplateAssignment::create(['template_id' => $this->template->id, 'class_id' => $this->class->id]);
    }

    private function actingAsRole(string $role): User
    {
        $user = User::create([
            'name' => ucfirst($role), 'email' => "$role@test.local", 'password' => bcrypt('x'),
            'role' => $role, 'is_active' => true, 'default_branch_id' => $this->branch->id,
        ]);
        $this->actingAs($user);

        return $user;
    }

    public function test_period_opens_empty_with_all_students_and_criteria(): void
    {
        $this->actingAsRole('super-admin');

        $res = $this->postJson('/api/monthly-evaluations/open', [
            'template_id' => $this->template->id, 'class_id' => $this->class->id,
            'period_year' => 2026, 'period_month' => 7,
        ])->assertOk();

        $res->assertJsonCount(12, 'students');
        $res->assertJsonCount(2, 'criteria');
        // The current period must start empty — no pre-filled scores.
        $this->assertEmpty($res->json('scores'));
    }

    public function test_validation_gate_blocks_submit_until_low_score_has_a_comment(): void
    {
        $user = $this->actingAsRole('super-admin');
        $eval = $this->open($user);
        $student = Student::first();

        // A low discipline score (< threshold 4) with NO comment must trip the gate.
        $this->putJson("/api/monthly-evaluations/{$eval->id}/scores", ['scores' => [
            ['student_id' => $student->id, 'criterion_id' => $this->discipline->id, 'score' => 2],
        ]])->assertOk();

        $gate = $this->getJson("/api/monthly-evaluations/{$eval->id}/validate")->assertOk();
        $this->assertFalse($gate->json('ok'));
        $this->assertSame('low', $gate->json('unmet.0.reason'));

        // Submit is rejected with the unmet list.
        $this->postJson("/api/monthly-evaluations/{$eval->id}/submit")
            ->assertStatus(422)
            ->assertJsonPath('ok', false);

        // Add the comment → gate clears → submit succeeds.
        $this->putJson("/api/monthly-evaluations/{$eval->id}/scores", ['scores' => [
            ['student_id' => $student->id, 'criterion_id' => $this->discipline->id, 'score' => 2, 'comment' => 'Repeated misconduct.'],
        ]])->assertOk();

        $this->postJson("/api/monthly-evaluations/{$eval->id}/submit")
            ->assertOk()
            ->assertJsonPath('ok', true);

        $this->assertSame('submitted', $eval->fresh()->status);
    }

    public function test_identical_scores_produce_low_confidence_and_review_flags(): void
    {
        $user = $this->actingAsRole('super-admin');
        $eval = $this->open($user);

        // Every student gets identical full marks → integrity should flag it.
        $scores = [];
        foreach (Student::all() as $s) {
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->discipline->id, 'score' => 10];
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->academic->id, 'score' => 100];
        }
        $this->putJson("/api/monthly-evaluations/{$eval->id}/scores", ['scores' => $scores])->assertOk();

        $res = $this->postJson("/api/monthly-evaluations/{$eval->id}/submit")->assertOk();
        // Identical scores must dent confidence and require review — the flag is
        // the real protection; it must never fast-track.
        $this->assertLessThan(80, $res->json('integrity.confidence'));
        $this->assertTrue($res->json('integrity.needs_review'));

        $flagTypes = ReviewFlag::where('monthly_evaluation_id', $eval->id)->pluck('type');
        $this->assertTrue($flagTypes->contains('identical'));
        $this->assertNotNull(ConfidenceScore::where('monthly_evaluation_id', $eval->id)->first());
    }

    public function test_healthy_submission_fast_tracks_to_approved(): void
    {
        $user = $this->actingAsRole('super-admin');
        $eval = $this->open($user);

        // Varied, realistic scores → high confidence, no flags → auto-approved.
        $scores = [];
        foreach (Student::all() as $i => $s) {
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->discipline->id, 'score' => 6 + ($i % 4)];
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->academic->id, 'score' => 60 + ($i % 30)];
        }
        $this->putJson("/api/monthly-evaluations/{$eval->id}/scores", ['scores' => $scores])->assertOk();

        // Simulate a genuine working session (~20 min) so the fast-completion
        // penalty doesn't fire on instant test writes.
        EvaluationScore::where('monthly_evaluation_id', $eval->id)->update(['created_at' => now()->subMinutes(20)]);
        $eval->update(['created_at' => now()->subMinutes(20)]);

        $res = $this->postJson("/api/monthly-evaluations/{$eval->id}/submit")->assertOk();
        $this->assertGreaterThanOrEqual(80, $res->json('integrity.confidence'));
        $this->assertFalse($res->json('integrity.needs_review'));
        $this->assertSame('approved', $eval->fresh()->status);
    }

    public function test_review_workflow_advances_to_published(): void
    {
        // A submitted evaluation awaiting review.
        $eval = MonthlyEvaluation::create([
            'branch_id' => $this->branch->id, 'template_id' => $this->template->id, 'class_id' => $this->class->id,
            'period_year' => 2026, 'period_month' => 6, 'status' => 'submitted', 'submitted_at' => now(), 'class_avg' => 62,
        ]);

        $this->actingAsRole('coordinator');
        $this->postJson("/api/eval-reviews/{$eval->id}", ['decision' => 'approve'])->assertOk();
        $this->assertSame('in_review', $eval->fresh()->status);

        $this->actingAsRole('principal');
        $this->postJson("/api/eval-reviews/{$eval->id}", ['decision' => 'approve'])->assertOk();
        $this->assertSame('approved', $eval->fresh()->status);

        $this->postJson("/api/eval-reviews/{$eval->id}", ['decision' => 'publish'])->assertOk();
        $this->assertSame('published', $eval->fresh()->status);

        // Three decisions recorded in the audit trail.
        $this->getJson("/api/eval-reviews/{$eval->id}/history")->assertOk()->assertJsonCount(3, 'data');
    }

    public function test_template_criteria_save_and_report(): void
    {
        $this->actingAsRole('super-admin');

        // Weight edit persists and totals 100.
        $this->putJson("/api/eval-templates/{$this->template->id}/criteria", ['criteria' => [
            ['name' => 'Discipline', 'weight' => 50, 'max_score' => 10, 'rating_type' => 'numeric', 'comment_rule' => 'optional'],
            ['name' => 'Academic', 'weight' => 50, 'max_score' => 100, 'rating_type' => 'percent', 'comment_rule' => 'optional'],
        ]])->assertOk()->assertJsonPath('weight_total', 100);

        $this->assertSame(2, EvaluationCriterion::where('template_id', $this->template->id)->count());
    }

    public function test_always_review_policy_prevents_fast_track(): void
    {
        \App\Support\SystemConfig::save(['eval_review_policy' => 'always']);
        $user = $this->actingAsRole('super-admin');
        $eval = $this->open($user);

        // Varied, realistic scores over a genuine session — would normally
        // fast-track, but the "always" policy forces review.
        $scores = [];
        foreach (Student::all() as $i => $s) {
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->discipline->id, 'score' => 6 + ($i % 4)];
            $scores[] = ['student_id' => $s->id, 'criterion_id' => $this->academic->id, 'score' => 60 + ($i % 30)];
        }
        $this->putJson("/api/monthly-evaluations/{$eval->id}/scores", ['scores' => $scores])->assertOk();
        EvaluationScore::where('monthly_evaluation_id', $eval->id)->update(['created_at' => now()->subMinutes(20)]);
        $eval->update(['created_at' => now()->subMinutes(20)]);

        $res = $this->postJson("/api/monthly-evaluations/{$eval->id}/submit")->assertOk();
        $this->assertGreaterThanOrEqual(80, $res->json('integrity.confidence'));
        $this->assertTrue($res->json('integrity.needs_review'));   // policy overrides confidence
        $this->assertSame('submitted', $eval->fresh()->status);    // not auto-approved
    }

    public function test_editing_criteria_after_an_evaluation_exists_forks_a_new_version(): void
    {
        $this->actingAsRole('super-admin');
        $this->assertSame(1, $this->template->fresh()->version);

        // An evaluation now references this template.
        MonthlyEvaluation::create([
            'branch_id' => $this->branch->id, 'template_id' => $this->template->id, 'class_id' => $this->class->id,
            'period_year' => 2026, 'period_month' => 5, 'status' => 'submitted',
        ]);

        // Editing criteria must fork a new version rather than mutate history.
        $this->putJson("/api/eval-templates/{$this->template->id}/criteria", ['criteria' => [
            ['name' => 'Discipline', 'weight' => 100, 'max_score' => 10, 'rating_type' => 'numeric', 'comment_rule' => 'optional'],
        ]])->assertOk();

        $this->assertSame(2, $this->template->fresh()->version);
    }

    public function test_dashboard_overview_and_csv_export(): void
    {
        $this->actingAsRole('super-admin');
        MonthlyEvaluation::create([
            'branch_id' => $this->branch->id, 'template_id' => $this->template->id, 'class_id' => $this->class->id,
            'period_year' => 2026, 'period_month' => 7, 'status' => 'published', 'class_avg' => 71,
        ]);

        $this->getJson('/api/eval-dashboard/overview')->assertOk()
            ->assertJsonStructure(['period', 'tiles' => ['completion', 'avg_confidence'], 'evaluators', 'trend']);

        $res = $this->get('/api/eval-dashboard/export/evaluators')->assertOk();
        $this->assertStringContainsString('text/csv', $res->headers->get('Content-Type'));
        $this->assertStringContainsString('Evaluator', $res->getContent());
    }

    private function open(User $user, int $month = 7): MonthlyEvaluation
    {
        $this->postJson('/api/monthly-evaluations/open', [
            'template_id' => $this->template->id, 'class_id' => $this->class->id,
            'period_year' => 2026, 'period_month' => $month,
        ])->assertOk();

        return MonthlyEvaluation::where('class_id', $this->class->id)->where('period_month', $month)->firstOrFail();
    }
}
