<?php

namespace Tests\Unit;

use App\Domain\Pricing\Concerns\ManagesDiscounts;
use App\Domain\Pricing\Services\DiscountCandidate;
use Tests\TestCase;

/**
 * What the discount picker is allowed to take off a price.
 *
 * The picker is authoritative once the receptionist has touched it — the engine
 * proposes, the desk disposes — and the total was summed straight from
 * `selectedDiscountIds`. That property is public, so it is a list the browser
 * sends: applyDiscount()'s refusal of a blocked rule, and its diversion of an
 * above-ceiling one into an approval request, were both worth nothing by the
 * time the money was worked out. The academy's global discount ceiling was
 * missing from that path too, so it governed only the discounts nobody chose.
 *
 * With no academy bound, SystemSetting::get() answers with its defaults: a 50%
 * global ceiling and a 10% manual ceiling for a receptionist.
 */
class DiscountPickerCeilingTest extends TestCase
{
    public function test_a_blocked_rule_in_the_selection_is_not_paid_for(): void
    {
        $till = $this->till([
            $this->candidate(1, DiscountCandidate::APPLIED, 10000),
            $this->candidate(2, DiscountCandidate::BLOCKED, 40000),
        ]);
        $till->selectedDiscountIds = [1, 2];

        $this->assertSame(10000, $till->selectedDiscountTotal());
    }

    public function test_a_rule_awaiting_approval_is_not_paid_for_either(): void
    {
        $till = $this->till([$this->candidate(1, DiscountCandidate::NEEDS_APPROVAL, 30000)]);
        $till->selectedDiscountIds = [1];

        $this->assertSame(0, $till->selectedDiscountTotal());
    }

    public function test_an_id_that_was_never_offered_is_ignored(): void
    {
        $till = $this->till([$this->candidate(1, DiscountCandidate::APPLIED, 10000)]);
        $till->selectedDiscountIds = [1, 99];

        $this->assertSame(10000, $till->selectedDiscountTotal());
    }

    public function test_stacked_discounts_stop_at_the_academys_ceiling(): void
    {
        // Three genuine 25% rules on a 1,000 EGP price is 75% — more than the
        // academy allows anyone to give, however it was assembled.
        $till = $this->till([
            $this->candidate(1, DiscountCandidate::APPLIED, 25000),
            $this->candidate(2, DiscountCandidate::STACKS, 25000),
            $this->candidate(3, DiscountCandidate::STACKS, 25000),
        ]);
        $till->selectedDiscountIds = [1, 2, 3];

        $this->assertSame(50000, $till->selectedDiscountTotal());
    }

    public function test_a_manual_discount_within_the_actors_ceiling_counts(): void
    {
        $till = $this->till([]);
        $till->manualDiscountAmount = '100';          // 100 EGP of 1,000 = 10%
        $till->manualDiscountReason = 'hardship';

        $this->assertSame(10000, $till->selectedDiscountTotal());
        $this->assertCount(1, $till->discountSnapshot());
    }

    public function test_a_manual_discount_above_the_actors_ceiling_reaches_neither_the_total_nor_the_invoice(): void
    {
        $till = $this->till([]);
        $till->manualDiscountAmount = '200';          // 200 EGP of 1,000 = 20%
        $till->manualDiscountReason = 'hardship';

        $this->assertFalse($till->validateManualDiscount());
        $this->assertSame(0, $till->selectedDiscountTotal());
        $this->assertSame([], $till->discountSnapshot());
    }

    public function test_the_snapshot_records_exactly_what_was_charged(): void
    {
        $till = $this->till([
            $this->candidate(1, DiscountCandidate::APPLIED, 10000, 'خصم الإخوة'),
            $this->candidate(2, DiscountCandidate::BLOCKED, 40000, 'منحة دراسية'),
        ]);
        $till->selectedDiscountIds = [1, 2];

        $this->assertSame(
            [['rule_id' => 1, 'name' => 'خصم الإخوة', 'discount' => 10000]],
            $till->discountSnapshot()
        );
    }

    // ---- helpers ----------------------------------------------------------

    /** A checkout screen with a 1,000 EGP item on it, operated by a receptionist. */
    private function till(array $candidates): object
    {
        $till = new class
        {
            use ManagesDiscounts;

            public int $roleLevel = 0;

            protected function actorRoleLevel(): int
            {
                return $this->roleLevel;
            }
        };

        $till->discountCandidates = $candidates;
        $till->discountBaseAmount = 100000;
        $till->discountFinalAmount = 100000;

        return $till;
    }

    private function candidate(int $id, string $state, int $discount, string $name = 'خصم'): array
    {
        return (new DiscountCandidate(
            ruleId: $id,
            name: $name,
            state: $state,
            discountAmount: $discount,
        ))->toArray();
    }
}
