<?php

namespace Tests\Feature;

use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Context\BranchScopeState;
use App\Domain\Shared\Models\Academy;
use App\Domain\Training\Models\Enrollment;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

/**
 * The catalogue is strictly branch-scoped, and a missing base price is a hard
 * failure that stops a sale. So the one thing worth pinning is that every live
 * enrolment can still be priced with the scope switched on.
 *
 *   DB_CONNECTION=pgsql DB_DATABASE=oc_sport_test ./vendor/bin/phpunit --filter PricingSurvivesBranchScopeTest
 */
class PricingSurvivesBranchScopeTest extends TestCase
{
    private ?BranchScopeState $state = null;

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

        if (config('database.default') !== 'pgsql') {
            $this->markTestSkipped('Needs a restored Postgres tenant.');
        }

        if (! $academy = Academy::first()) {
            $this->markTestSkipped('No academy in the restored tenant.');
        }

        app()->instance('current_academy', $academy);
        $this->state = app(BranchScopeState::class);
    }

    protected function tearDown(): void
    {
        $this->state?->deactivate();
        parent::tearDown();
    }

    public function test_every_active_enrolment_still_prices_inside_its_own_branch(): void
    {
        $pricing = app(PricingService::class);

        // Which branches actually have live enrolments to price.
        $branches = DB::table('enrollments')
            ->join('training_groups', 'training_groups.id', '=', 'enrollments.training_group_id')
            ->where('enrollments.status', 'active')
            ->whereNotNull('training_groups.branch_id')
            ->distinct()
            ->pluck('training_groups.branch_id');

        $failures = [];
        $priced = 0;

        foreach ($branches as $branchId) {
            $this->state->activate((int) $branchId);

            $enrolments = Enrollment::with('group.program', 'participant')
                ->where('status', 'active')
                ->get();

            foreach ($enrolments as $enrolment) {
                $programme = $enrolment->group?->program;

                if (! $programme) {
                    // The group or its programme is not visible from this
                    // branch — that is a data problem this test should surface,
                    // not swallow.
                    $failures["enrolment #{$enrolment->id}"] = 'group or programme not visible in branch '.$branchId;
                    continue;
                }

                try {
                    $pricing->calculate($programme, $enrolment->participant, (int) $branchId);
                    $priced++;
                } catch (DomainException $e) {
                    $key = "programme #{$programme->id} in branch {$branchId}";
                    $failures[$key] = $e->getMessage();
                }
            }
        }

        // A programme that had no price before this change still has none — that
        // is pre-existing and not what this test is guarding. What it guards is
        // that scoping did not take a price away, so compare against the same
        // question asked with the scope off.
        $this->state->deactivate();
        $preExisting = [];

        foreach (array_keys($failures) as $key) {
            if (! preg_match('/programme #(\d+) in branch (\d+)/', $key, $m)) {
                continue;
            }

            $hasAnyPrice = DB::table('base_prices')
                ->where('priceable_type', 'like', '%TrainingProgram')
                ->where('priceable_id', $m[1])
                ->where('is_active', true)
                ->exists();

            if (! $hasAnyPrice) {
                $preExisting[] = $key;
            }
        }

        $caused = array_diff_key($failures, array_flip($preExisting));

        $this->assertSame(
            [],
            $caused,
            "Branch scoping took a price away from a live enrolment:\n"
                .json_encode($caused, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
        );

        $this->assertGreaterThan(0, $priced, 'Nothing priced at all — the test proved nothing.');

        if ($preExisting !== []) {
            fwrite(STDERR, "\nPre-existing (no active base price at all, unrelated to branch scoping):\n  "
                .implode("\n  ", $preExisting)."\n");
        }
    }
}
