<?php
// BencodeSerialisableTest.php
// Created: 2024-09-29
// Updated: 2025-04-02

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
use Index\Bencode\{Bencode,BencodeProperty,BencodeSerializable,BencodeSerializableCommon};

#[CoversClass(BencodeProperty::class)]
#[CoversClass(BencodeSerializableCommon::class)]
#[UsesClass(Bencode::class)]
#[UsesClass(BencodeSerializable::class)]
final class BencodeSerialisableTest extends TestCase {
    private const BASIC_BENCODED_ENCODED = 'd9:stringVal12:string value6:intVali1234e9:intStrVal4:56788:floatVal5:12.3411:floatStrVal5:56.787:trueVali1e22:truePossiblyOmittedVali1e8:falseVali0e14:nullValPresent10:scalarValsli0ei1234e5:12.343:stre16:stringVal_method12:string value9:getIntVali1234e11:getFloatVal5:12.3417:getFloatStringVal5:56.7810:getTrueVali1e25:getTruePossiblyOmittedVali1e11:getFalseVali0e17:getNullValPresent13:getScalarValsli0ei1234e5:12.343:stre5:innerd3:objd3:str21:wow this is illegibleee15:getIntStringVal4:5678e';

    public function testBasicBencodedObject(): void {
        $test = new class implements BencodeSerializable {
            use BencodeSerializableCommon;

            #[BencodeProperty]
            public string $stringVal = 'string value';

            #[BencodeProperty]
            public int $intVal = 1234;

            #[BencodeProperty(numbersAsString: true)]
            public int $intStrVal = 5678;

            #[BencodeProperty]
            public float $floatVal = 12.34;

            #[BencodeProperty(numbersAsString: true)]
            public float $floatStrVal = 56.78;

            #[BencodeProperty]
            public bool $trueVal = true;

            #[BencodeProperty(omitIfValue: false)]
            public bool $truePossiblyOmittedVal = true;

            #[BencodeProperty]
            public bool $falseVal = false;

            #[BencodeProperty(omitIfValue: false)]
            public bool $falseOmittedVal = false;

            #[BencodeProperty]
            public mixed $nullValOmitted = null;

            #[BencodeProperty(omitIfNull: false)]
            public mixed $nullValPresent = null;

            /** @var array<?scalar> */
            #[BencodeProperty]
            public array $scalarVals = [null, 0, 1234, 12.34, 'str', true, false];

            #[BencodeProperty('stringVal_method')]
            public function getStringVal(): string {
                return 'string value';
            }

            #[BencodeProperty]
            public function getIntVal(): int {
                return 1234;
            }

            #[BencodeProperty(order: 1, numbersAsString: true)]
            public function getIntStringVal(): int {
                return 5678;
            }

            #[BencodeProperty]
            public function getFloatVal(): float {
                return 12.34;
            }

            #[BencodeProperty(numbersAsString: true)]
            public function getFloatStringVal(): float {
                return 56.78;
            }

            #[BencodeProperty]
            public function getTrueVal(): bool {
                return true;
            }

            #[BencodeProperty(omitIfValue: false)]
            public function getTruePossiblyOmittedVal(): bool {
                return true;
            }

            #[BencodeProperty]
            public function getFalseVal(): bool {
                return false;
            }

            #[BencodeProperty(omitIfValue: false)]
            public function getFalseOmittedVal(): bool {
                return false;
            }

            #[BencodeProperty]
            public function getNullValOmitted(): mixed {
                return null;
            }

            #[BencodeProperty(omitIfNull: false)]
            public function getNullValPresent(): mixed {
                return null;
            }

            /** @return array<?scalar> */
            #[BencodeProperty]
            public function getScalarVals(): array {
                return [null, 0, 1234, 12.34, 'str', true, false];
            }

            #[BencodeProperty('inner')]
            public function getObject(): object {
                return new class implements BencodeSerializable {
                    use BencodeSerializableCommon;

                    #[BencodeProperty('obj')]
                    public function getObject(): object {
                        return new class implements BencodeSerializable {
                            use BencodeSerializableCommon;

                            #[BencodeProperty('str')]
                            public function getString(): string {
                                return 'wow this is illegible';
                            }
                        };
                    }
                };
            }

            public function methodWithArguments(string $meow): string {
                return $meow;
            }

            public static function staticMethodWithArguments(string $meow): string {
                return $meow;
            }
        };

        $this->assertEquals(self::BASIC_BENCODED_ENCODED, Bencode::encode($test));
    }

    public function testDoubleBencodedProperty(): void {
        $this->expectException(RuntimeException::class);

        $test = new class implements BencodeSerializable {
            use BencodeSerializableCommon;

            #[BencodeProperty('test1')]
            #[BencodeProperty('test2')] // @phpstan-ignore attribute.nonRepeatable
            public string $stringVal = 'string value';

            #[BencodeProperty('test3')]
            #[BencodeProperty('test4')] // @phpstan-ignore attribute.nonRepeatable
            public function getIntVal(): int {
                return 1234;
            }
        };

        Bencode::encode($test);
    }

    public function testDuplicateBencodedProperty(): void {
        $this->expectException(RuntimeException::class);

        $test = new class implements BencodeSerializable {
            use BencodeSerializableCommon;

            #[BencodeProperty]
            public string $test1 = 'string value';

            #[BencodeProperty('test1')]
            public function getIntVal(): int {
                return 1234;
            }
        };

        Bencode::encode($test);
    }
}