<?php

declare(strict_types=1);

namespace Tests\Feature\Schema;

use App\Models\Fee;
use App\Models\Plan;
use App\Support\Money\Money;
use Illuminate\Foundation\Testing\RefreshDatabase;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * The Eloquent money cast.
 *
 * Its documented contract and its behaviour had drifted apart: the comment said
 * a bare integer string was rejected as ambiguous, and the code silently treated
 * it as major units. For a currency with two decimals that is a hundredfold
 * pricing error waiting for the first caller who assumed the other reading.
 */
final class MoneyCastTest extends TestCase
{
    use RefreshDatabase;

    #[Test]
    public function an_integer_is_read_as_minor_units(): void
    {
        $fee = Fee::factory()->create();
        $fee->fixed_amount_minor = 1_900;
        $fee->save();

        $this->assertSame(1_900, (int) $fee->fresh()->fixed_amount_minor);
    }

    #[Test]
    public function a_decimal_string_is_read_as_major_units(): void
    {
        $plan = Plan::factory()->create();
        $plan->price_minor = '19.00';
        $plan->save();

        $this->assertSame(1_900, $plan->fresh()->price_minor->minor);
    }

    /**
     * The ambiguity the cast now refuses rather than guessing at.
     */
    #[Test]
    public function a_bare_integer_string_is_refused_for_a_decimal_currency(): void
    {
        $plan = Plan::factory()->create(['currency' => 'USD']);

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Ambiguous amount');

        $plan->price_minor = '1900';
    }

    /**
     * A zero-decimal currency has no separator to offer, so an integer string is
     * unambiguous and must still be accepted.
     */
    #[Test]
    public function a_bare_integer_string_is_accepted_for_a_zero_decimal_currency(): void
    {
        $plan = Plan::factory()->create(['currency' => 'JPY']);
        $plan->price_minor = '12500';
        $plan->save();

        $this->assertSame(12_500, $plan->fresh()->price_minor->minor);
    }

    #[Test]
    public function it_refuses_an_amount_in_the_wrong_currency(): void
    {
        $plan = Plan::factory()->create(['currency' => 'USD']);

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

        $plan->price_minor = Money::of(1_900, 'EUR');
    }

    #[Test]
    public function a_money_object_round_trips_through_the_cast(): void
    {
        $plan = Plan::factory()->create(['currency' => 'USD']);
        $plan->price_minor = Money::fromMajorString('99.00', 'USD');
        $plan->save();

        $read = $plan->fresh()->price_minor;

        $this->assertInstanceOf(Money::class, $read);
        $this->assertSame(9_900, $read->minor);
        $this->assertSame('USD', $read->currency);
    }

    #[Test]
    public function a_null_amount_stays_null(): void
    {
        $fee = Fee::factory()->create();
        $fee->fixed_amount_minor = null;
        $fee->save();

        $this->assertNull($fee->fresh()->fixed_amount_minor);
    }
}
