<?php

namespace Tests\Feature;

use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingGroupService;
use App\Domain\Training\Services\TrainingProgramService;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;

/**
 * Deleting a programme or a group must never take enrolments with it.
 *
 * On 28 August 2026 OC-Sport reorganised its season: the existing programmes
 * were deleted and recreated under new names. Both delete paths called
 * `$group->enrollments()->forceDelete()`, so 181 enrolment rows went with them
 * — hard-deleted, no soft delete, no audit row, nothing to restore from.
 *
 * `enrollments` is the only table GenerateRenewalInvoices bills from. The
 * players were still training, still members, still owed money; they simply
 * stopped existing as far as billing was concerned. On 2 September the renewal
 * run raised 229 invoices and reported a clean success, and 95 paying players
 * were not among them. Nobody found out until a parent asked why no bill had
 * come for his son.
 *
 * A destructive delete is now refused while anything is enrolled. These tests
 * pin the refusal, and — more importantly — pin that the enrolments are still
 * there afterwards.
 */
class ProgramDeleteKeepsEnrolmentsTest extends TestCase
{
    private const ACADEMY = 1;
    private const BRANCH = 1;
    private const PROGRAM = 7;
    private const GROUP = 3;

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

        if (config('database.default') !== 'sqlite') {
            $this->markTestSkipped('Builds its own schema; runs on the in-memory SQLite connection.');
        }

        $this->createSchema();
        $this->seedProgramme();
    }

    public function test_a_programme_with_an_active_enrolment_cannot_be_deleted(): void
    {
        $this->enrol(1, 'active');

        try {
            app(TrainingProgramService::class)->delete(TrainingProgram::findOrFail(self::PROGRAM));
            $this->fail('The delete went through and took a paying player off the billing run with it.');
        } catch (DomainException $e) {
            $this->assertStringContainsString('اشتراك نشط', $e->getMessage());
        }

        $this->assertSame(1, Enrollment::count(), 'The enrolment survives the refused delete.');
        $this->assertNotNull(TrainingProgram::find(self::PROGRAM));
    }

    public function test_a_programme_whose_enrolments_are_all_cancelled_still_cannot_be_deleted(): void
    {
        // A cancelled enrolment is the only record of what an issued invoice
        // bought, and issued invoices are frozen. Destroying it leaves money in
        // the ledger pointing at nothing.
        $this->enrol(1, 'cancelled');

        $this->expectException(DomainException::class);
        $this->expectExceptionMessageMatches('/فواتير صادرة/');

        app(TrainingProgramService::class)->delete(TrainingProgram::findOrFail(self::PROGRAM));
    }

    public function test_a_group_with_an_enrolment_cannot_be_deleted(): void
    {
        $this->enrol(1, 'active');

        try {
            app(TrainingGroupService::class)->delete(TrainingGroup::findOrFail(self::GROUP));
            $this->fail('The group delete went through and took the enrolment with it.');
        } catch (DomainException $e) {
            $this->assertStringContainsString('اشتراك نشط', $e->getMessage());
        }

        $this->assertSame(1, Enrollment::count());
        $this->assertNotNull(TrainingGroup::find(self::GROUP));
    }

    public function test_an_enrolment_in_another_branch_still_blocks_the_delete(): void
    {
        // The guard counts without the branch scope on purpose. An enrolment
        // hidden because the user is looking at another branch is still an
        // enrolment, and reading zero because of it is how a guard like this
        // fails open — quietly, and only in the multi-branch clubs.
        $this->enrol(1, 'active', branchId: 2);

        $this->expectException(DomainException::class);

        app(TrainingProgramService::class)->delete(TrainingProgram::findOrFail(self::PROGRAM));
    }

    public function test_a_programme_nobody_was_enrolled_in_is_still_deletable(): void
    {
        // The whole point of keeping delete around: a programme created by
        // mistake, with nothing riding on it.
        app(TrainingProgramService::class)->delete(TrainingProgram::findOrFail(self::PROGRAM));

        $this->assertNull(TrainingProgram::withTrashed()->find(self::PROGRAM));
        $this->assertNull(TrainingGroup::withTrashed()->find(self::GROUP));
    }

    public function test_a_group_nobody_was_enrolled_in_is_still_deletable(): void
    {
        app(TrainingGroupService::class)->delete(TrainingGroup::findOrFail(self::GROUP));

        $this->assertNull(TrainingGroup::withTrashed()->find(self::GROUP));
        $this->assertNotNull(TrainingProgram::find(self::PROGRAM), 'Deleting a group leaves its programme alone.');
    }

    // ---- fixtures ---------------------------------------------------------

    private function enrol(int $id, string $status, int $branchId = self::BRANCH): void
    {
        DB::table('enrollments')->insert([
            'id' => $id, 'academy_id' => self::ACADEMY, 'branch_id' => $branchId,
            'participant_id' => $id, 'training_group_id' => self::GROUP,
            'training_program_id' => self::PROGRAM,
            'enrollment_date' => '2026-07-01', 'start_date' => '2026-07-01',
            'status' => $status, 'payment_status' => 'pending',
            'next_billing_date' => '2026-09-01',
            'created_at' => now(), 'updated_at' => now(),
        ]);
    }

    private function seedProgramme(): void
    {
        DB::table('training_programs')->insert([
            'id' => self::PROGRAM, 'academy_id' => self::ACADEMY, 'branch_id' => self::BRANCH,
            'name' => 'Academy 2017-2018', 'name_ar' => 'اكاديمية 2017 -2018',
            'slug' => 'academy-2017-2018', 'status' => 'active',
            'renewal_policy' => 'auto_renew', 'billing_cycle' => 'monthly',
            'created_at' => now(), 'updated_at' => now(),
        ]);

        DB::table('training_groups')->insert([
            'id' => self::GROUP, 'academy_id' => self::ACADEMY, 'branch_id' => self::BRANCH,
            'training_program_id' => self::PROGRAM, 'name' => 'G1', 'name_ar' => 'مجموعة',
            'code' => 'ACAD01', 'status' => 'active',
            'created_at' => now(), 'updated_at' => now(),
        ]);
    }

    private function createSchema(): void
    {
        Schema::create('academies', function (Blueprint $t) {
            $t->id();
            $t->string('name')->nullable();
            $t->timestamps();
        });
        DB::table('academies')->insert(['id' => self::ACADEMY, 'name' => 'OC']);

        Schema::create('training_programs', function (Blueprint $t) {
            $t->id();
            $t->uuid('uuid')->nullable();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('branch_id')->nullable();
            $t->string('name')->nullable();
            $t->string('name_ar')->nullable();
            $t->string('slug')->nullable();
            $t->string('status')->default('active');
            $t->string('renewal_policy')->nullable();
            $t->string('billing_cycle')->nullable();
            $t->unsignedBigInteger('created_by')->nullable();
            $t->timestamps();
            $t->softDeletes();
        });

        Schema::create('training_groups', function (Blueprint $t) {
            $t->id();
            $t->uuid('uuid')->nullable();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('branch_id')->nullable();
            $t->unsignedBigInteger('training_program_id')->nullable();
            $t->string('name')->nullable();
            $t->string('name_ar')->nullable();
            $t->string('code')->nullable();
            $t->string('status')->default('active');
            $t->timestamp('status_changed_at')->nullable();
            $t->unsignedInteger('current_count')->default(0);
            $t->unsignedInteger('waitlist_count')->default(0);
            $t->unsignedBigInteger('created_by')->nullable();
            $t->timestamps();
            $t->softDeletes();
        });

        Schema::create('enrollments', function (Blueprint $t) {
            $t->id();
            $t->uuid('uuid')->nullable();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('branch_id')->nullable();
            $t->unsignedBigInteger('participant_id')->nullable();
            $t->unsignedBigInteger('training_group_id')->nullable();
            $t->unsignedBigInteger('training_program_id')->nullable();
            $t->unsignedBigInteger('transferred_to_id')->nullable();
            $t->date('enrollment_date')->nullable();
            $t->date('start_date')->nullable();
            $t->date('next_billing_date')->nullable();
            $t->string('status')->default('active');
            $t->string('payment_status')->nullable();
            $t->unsignedBigInteger('enrolled_by')->nullable();
            $t->unsignedBigInteger('invoice_id')->nullable();
            $t->json('metadata')->nullable();
            $t->timestamps();
        });

        Schema::create('training_sessions', function (Blueprint $t) {
            $t->id();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('training_group_id')->nullable();
            $t->date('session_date')->nullable();
            $t->timestamps();
        });

        Schema::create('training_schedules', function (Blueprint $t) {
            $t->id();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('training_group_id')->nullable();
            $t->unsignedBigInteger('facility_id')->nullable();
            $t->unsignedTinyInteger('day_of_week')->nullable();
            $t->timestamps();
        });

        Schema::create('waitlists', function (Blueprint $t) {
            $t->id();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('training_group_id')->nullable();
            $t->unsignedBigInteger('participant_id')->nullable();
            $t->timestamps();
        });

        Schema::create('assignments', function (Blueprint $t) {
            $t->id();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->string('assignable_type')->nullable();
            $t->unsignedBigInteger('assignable_id')->nullable();
            $t->timestamps();
            $t->softDeletes();
        });

        Schema::create('audit_logs', function (Blueprint $t) {
            $t->id();
            $t->unsignedBigInteger('academy_id')->nullable();
            $t->unsignedBigInteger('user_id')->nullable();
            $t->string('auditable_type')->nullable();
            $t->unsignedBigInteger('auditable_id')->nullable();
            $t->string('action')->nullable();
            $t->json('old_values')->nullable();
            $t->json('new_values')->nullable();
            $t->string('ip_address')->nullable();
            $t->text('user_agent')->nullable();
            $t->text('url')->nullable();
            $t->boolean('is_financial')->default(false);
            $t->timestamp('created_at')->nullable();
        });
    }
}
