<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

class Varien_Simplexml_ElementTest extends TestCase {
    const TRAVERSAL_XML = <<<'XML'
<?xml version="1.0"?>
<config>
  <general>
    <section translate="label">
      <label>Test</label>
      <nested>
        <item>
          <label>Foo</label>
        </item>
        <item>
          <label>Bar</label>
        </item>
      </nested>
    </section>
    <section translate="label" module="Test">
      <label>More</label>
      <nested>
        <item>
          <label>Baz</label>
        </item>
        <item>
          <label>Qux</label>
        </item>
      </nested>
    </section>
  </general>
  <admin>
    <section>
      <label>Admin</label>
      <nested>
        <item>
          <label>Single</label>
        </item>
      </nested>
    </section>
  </admin>
</config>
XML;

    public function asIntProvider(): array {
        return [
            [["#" => "42"], 42],
            [["#" => "42extra"], 42],
            [["#" => "    42"], 42],
            [["#" => "42  "], 42],
            [["#" => "  42  "], 42],
            [["#" => "extra"], 0],
            [["#" => ""], 0],
            [["#" => "0"], 0],
            [["#" => "false"], 0],
            [["#" => "true"], 0],
            [["#" => "1"], 1],
        ];

    }
    public function asBoolProvider(): array {
        return [
            [["#" => "1"], true],
            [["#" => "true"], true],
            [["#" => "0"], false],
            [["#" => ""], false],
            [["#" => "extra"], false],
            [["#" => "false"], false],
            [["#" => "23"], false],
        ];
    }

    /**
     * @dataProvider asIntProvider
     */
    public function testAsInt(array $nodeData, mixed $expected): void {
        $el = new Varien_Simplexml_Element("foo", [$nodeData], Varien_Simplexml_Element::ITER_CHILDREN);

        $this->assertSame($expected, $el->asInt());
    }

    public function testAsIntThrow(): void {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage("Attempting to convert non-leaf object Varien_Simplexml_Element to string");

        $el = new Varien_Simplexml_Element("foo", [
            ["a" => [["nested" => [["#" => "testdata"]]]]],
        ], Varien_Simplexml_Element::ITER_CHILDREN);

        $el->asInt();
    }

    /**
     * @dataProvider asBoolProvider
     */
    public function testAsBool(array $nodeData, mixed $expected): void {
        $el = new Varien_Simplexml_Element("foo", [$nodeData], Varien_Simplexml_Element::ITER_CHILDREN);

        $this->assertSame($expected, $el->asBool());
    }

    public function testAsBoolThrow(): void {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage("Attempting to convert non-leaf object Varien_Simplexml_Element to string");

        $el = new Varien_Simplexml_Element("foo", [
            ["a" => [["nested" => [["#" => "testdata"]]]]],
        ], Varien_Simplexml_Element::ITER_CHILDREN);

        $el->asBool();
    }

    public function testTraversalVarien(): void {
        $config = new Varien_Simplexml_Config(self::TRAVERSAL_XML);

        $this->doTraversalTest($config->getNode(), Varien_Simplexml_Element::class);
    }

    public function testTraversalSimplexml(): void {
        $config = new SimpleXMLElement(self::TRAVERSAL_XML);

        $this->doTraversalTest($config, SimpleXMLElement::class);
    }

    /**
     * @param class-string $elementType
     */
    private function doTraversalTest(
        SimpleXMLElement|Varien_Simplexml_Element $node,
        string $elementType
    ): void {
        // Direct traversal
        $this->assertInstanceOf($elementType, $node);
        $this->assertTrue(isset($node->general));
        $this->assertInstanceOf($elementType, $node->general);
        $this->assertTrue(isset($node->general->section));
        $this->assertInstanceOf($elementType, $node->general->section);
        $this->assertTrue(isset($node->general->section->nested));
        $this->assertInstanceOf($elementType, $node->general->section->nested);
        $this->assertTrue(isset($node->general->section->nested->item));
        $this->assertInstanceOf($elementType, $node->general->section->nested->item);
        $this->assertTrue(isset($node->general->section->nested->item->label));
        $this->assertInstanceOf($elementType, $node->general->section->nested->item->label);
        $this->assertEquals("Foo", (string)$node->general->section->nested->item->label);
        $this->assertFalse(isset($node->general->section->nested->item->label->nothing));
        $this->assertEquals("", (string)$node->general->section->nested->item->label->nothing);
        $this->assertTrue(isset($node->admin));
        $this->assertInstanceOf($elementType, $node->admin);
        $this->assertTrue(isset($node->admin->section));
        $this->assertInstanceOf($elementType, $node->admin->section);
        $this->assertTrue(isset($node->admin->section->label));
        $this->assertInstanceOf($elementType, $node->admin->section->label);
        $this->assertEquals("Admin", (string)$node->admin->section->label);
        $this->assertFalse(isset($node->admin->section->label->another));
        $this->assertEquals("", (string)$node->admin->section->label->another);
        $this->assertFalse(isset($node->otherRoot));
        $this->assertEquals("", (string)$node->otherRoot);
    }

    public function testIterationVarien(): void {
        $config = new Varien_Simplexml_Config(self::TRAVERSAL_XML);

        $this->doIterationTest($config->getNode(), Varien_Simplexml_Element::class);
    }

    public function testIterationSimplexml(): void {
        $config = new SimpleXMLElement(self::TRAVERSAL_XML);

        $this->doIterationTest($config, SimpleXMLElement::class);
    }

    /**
     * @param class-string $elementType
     */
    private function doIterationTest(
        SimpleXMLElement|Varien_Simplexml_Element $node,
        string $elementType
    ): void {
        $idx = 0;
        $expectedItems = ["Foo", "Bar", "Baz", "Qux"];
        $config = [];

        foreach($node->general as $k => $n) {
            $this->assertInstanceOf($elementType, $n);
            $this->assertEquals("general", $n->getName());
            $this->assertEquals("general", $k);
            $general = [];

            foreach($n->section as $k => $s) {
                $this->assertInstanceOf($elementType, $s);
                $this->assertEquals("section", $s->getName());
                $this->assertEquals("section", $k);
                $section = [];

                foreach($s->nested as $k => $ne) {
                    $this->assertInstanceOf($elementType, $ne);
                    $this->assertEquals("nested", $ne->getName());
                    $this->assertEquals("nested", $k);
                    $nested = [];

                    foreach($ne->item as $k => $i) {
                        $this->assertInstanceOf($elementType, $i);
                        $this->assertEquals("item", $i->getName());
                        $this->assertEquals("item", $k);
                        $item = [];

                        foreach($i->label as $k => $l) {
                            $this->assertInstanceOf($elementType, $l);
                            $this->assertEquals("label", $l->getName());
                            $this->assertEquals("label", $k);
                            $this->assertEquals($expectedItems[$idx++], (string)$l);
                            $item[] = (string)$l;
                        }

                        $nested[] = $item;
                    }

                    $section[] = $nested;
                }

                $general[] = $section;
            }

            $config[] = $general;
        }

        $this->assertEquals([
            [
                [
                    [
                        ["Foo"], ["Bar"],
                    ],
                ],
                [
                    [
                        ["Baz"], ["Qux"],
                    ],
                ],
            ],
        ], $config);
    }

    public function testAsArray(): void {
        $config = new Varien_Simplexml_Config(self::TRAVERSAL_XML);

        $this->assertEquals([
            "general" => [
                "section" => [
                    "label" => "Test",
                    "nested" => [
                        "item" => [
                            "label" => "Foo",
                        ],
                    ],
                    "@" => [
                        "translate" => "label",
                    ],
                ],
            ],
            "admin" => [
                "section" => [
                    "label" => "Admin",
                    "nested" => [
                        "item" => [
                            "label" => "Single",
                        ],
                    ],
                ],
            ],
        ], $config->getNode()->asArray());
    }

    public function testAsCanonicalArray(): void {
        $config = new Varien_Simplexml_Config(self::TRAVERSAL_XML);

        $this->assertEquals([
            "general" => [
                "section" => [
                    "label" => "Test",
                    "nested" => [
                        "item" => [
                            "label" => "Foo",
                        ],
                    ],
                ],
            ],
            "admin" => [
                "section" => [
                    "label" => "Admin",
                    "nested" => [
                        "item" => [
                            "label" => "Single",
                        ],
                    ],
                ],
            ],
        ], $config->getNode()->asCanonicalArray());
    }

    public function testSet(): void {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage("Attempting to set property Varien_Simplexml_Element::bar");
        $element = new Varien_Simplexml_Element("foo", [], Varien_Simplexml_Element::ITER_CHILDREN);

        $element->bar = "baz";
    }

    public function testUnset(): void {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage("Attempting to unset property Varien_Simplexml_Element::bar");
        $element = new Varien_Simplexml_Element("foo", [[
            "bar" => [["#" => "Baz"]]
        ]], Varien_Simplexml_Element::ITER_CHILDREN);

        unset($element->bar);
    }

    public function testChildIteration(): void {
        $element = new Varien_Simplexml_Element("foo", [[
            "bar" => [["#" => "Baz"]],
            "baz" => [["#" => "Qux"]],
        ], [
            "otherNode" => [["#" => "Should not be included"]]
        ]], Varien_Simplexml_Element::ITER_CHILDREN);

        foreach($element as $node) {
            $items[] = [
                "name" => $node->getName(),
                "content" => (string)$node,
            ];
        }

        $this->assertEquals([
            ["name" => "bar", "content" => "Baz"],
            ["name" => "baz", "content" => "Qux"],
        ], $items);
    }

    public function testSelfIteration(): void {
        $element = new Varien_Simplexml_Element("foo", [[
            "bar" => [["#" => "Baz"]],
            "baz" => [["#" => "Should not be included"]],
        ], [
            "otherNode" => [["#" => "Qux"]]
        ]], Varien_Simplexml_Element::ITER_SELF);

        foreach($element as $node) {
            $items[] = [
                "name" => $node->getName(),
                "childrenCount" => count($node),
            ];
        }

        $this->assertEquals([
            ["name" => "foo", "childrenCount" => 2],
            ["name" => "foo", "childrenCount" => 1],
        ], $items);
    }
}
