<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\Entity;

use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Debtor;
use Ferienpass\CoreBundle\Entity\Offer\Base;
use Ferienpass\CoreBundle\Entity\Payment;
use Ferienpass\CoreBundle\Entity\PaymentItem;
use Ferienpass\CoreBundle\Entity\User;
use PHPUnit\Framework\TestCase;

final class PaymentItemTest extends TestCase
{
    public function testGetAmountReturnsAmount(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());

        self::assertSame(500, $item->getAmount());
    }

    public function testSetAmountUpdatesAmount(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());
        $item->setAmount(1000);

        self::assertSame(1000, $item->getAmount());
    }

    public function testGetTypeDefaultsToAttendance(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());

        self::assertSame('attendance', $item->getType());
    }

    public function testIsAttendanceReturnsTrueForDefaultType(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());

        self::assertTrue($item->isAttendance());
    }

    public function testIsDiscountReturnsFalseForAttendanceType(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());

        self::assertFalse($item->isDiscount());
    }

    public function testIsDiscountReturnsTrueForDiscountType(): void
    {
        $item = new PaymentItem(null, -200, type: 'discount');

        self::assertTrue($item->isDiscount());
        self::assertFalse($item->isAttendance());
    }

    public function testGetAttendanceReturnsAttendance(): void
    {
        $attendance = $this->makeAttendance();
        $item = new PaymentItem(null, 500, $attendance);

        self::assertSame($attendance, $item->getAttendance());
    }

    public function testRemoveAttendanceAssociationSetsNull(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());
        $item->removeAttendanceAssociation();

        self::assertNull($item->getAttendance());
    }

    public function testGetIdIsNullByDefault(): void
    {
        $item = new PaymentItem(null, 500, $this->makeAttendance());

        self::assertNull($item->getId());
    }

    public function testConstructorThrowsWhenAttendanceTypeWithoutAttendance(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        new PaymentItem(null, 500, null, 'attendance');
    }

    public function testConstructorAllowsReversalLineWithoutAttendance(): void
    {
        // A reversal (negative amount) may reference an attendance that was
        // deleted after the original receipt was issued — the money still has to
        // be handed back. It must not throw and renders as "Gelöschte Teilnahme".
        $item = new PaymentItem(null, -500, null, 'attendance');

        self::assertSame(-500, $item->getAmount());
        self::assertNull($item->getAttendance());
        self::assertTrue($item->isAttendance());
    }

    public function testConstructorThrowsWhenDiscountTypeWithPositiveAmount(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        new PaymentItem(null, 100, null, 'discount');
    }

    public function testGetPaymentReturnsPayment(): void
    {
        $user = new User();
        $debtor = new Debtor($user);
        $payment = new Payment($debtor);
        $attendance = $this->makeAttendance();
        $item = new PaymentItem($payment, 500, $attendance);

        self::assertSame($payment, $item->getPayment());
    }

    public function testGetCreatedAtIsSet(): void
    {
        $before = new \DateTimeImmutable();
        $item = new PaymentItem(null, 500, $this->makeAttendance());
        $after = new \DateTimeImmutable();

        self::assertGreaterThanOrEqual($before, $item->getCreatedAt());
        self::assertLessThanOrEqual($after, $item->getCreatedAt());
    }

    private function makeAttendance(): Attendance
    {
        return new Attendance(new Base(), null);
    }
}
