<?php
declare(strict_types=1);

// operators/pdo.php calls a global logger() helper that consumers (apexl/io)
// provide; vault itself has no logging dependency. Bracketed namespace syntax is
// required to declare a global function alongside the test class.
namespace {
    if (!function_exists('logger')) {
        function logger(?string $channel = null): object
        {
            return new class () {
                public function __call(string $name, array $arguments): void
                {
                }
            };
        }
    }
}

namespace Apexl\Vault\Tests {
    use Apexl\Vault\Interfaces\ResultInterface;
    use Apexl\Vault\Operators\Drivers\SqLite;
    use PHPUnit\Framework\Attributes\Test;
    use PHPUnit\Framework\TestCase;
    use stdClass;

    /**
     * execute() used to return $this, so every query on a vault handed back the same
     * object and the rows you fetched were whichever query ran last:
     *
     *     $a = $db->select('x')->execute();
     *     $b = $db->select('y')->execute();   // $a === $b
     *     $a->fetch();                        // rows from y, silently
     *
     * That is why Etl/SyncedRecordFinder had to clone its driver — without the clone
     * the next select() anywhere in the process retargeted it and the finder reported
     * an existing row as missing. Results are separate objects now, so the failure is
     * no longer expressible.
     */
    final class ResultIndependenceTest extends TestCase
    {
        private SqLite $drv;

        protected function setUp(): void
        {
            if (!extension_loaded('pdo_sqlite')) {
                $this->markTestSkipped('ext-pdo_sqlite not available');
            }

            $cfg = new stdClass();
            $cfg->driver = 'sqlite';

            $this->drv = new SqLite($cfg);
            $this->drv->executeSql('DROP TABLE IF EXISTS reds');
            $this->drv->executeSql('DROP TABLE IF EXISTS blues');
            $this->drv->executeSql('CREATE TABLE reds (id INTEGER PRIMARY KEY, name TEXT)');
            $this->drv->executeSql('CREATE TABLE blues (id INTEGER PRIMARY KEY, name TEXT)');
            $this->drv->executeSql("INSERT INTO reds (id, name) VALUES (1, 'red')");
            $this->drv->executeSql("INSERT INTO blues (id, name) VALUES (1, 'blue')");
        }

        #[Test]
        public function twoQueriesOnOneVaultProduceTwoResults(): void
        {
            $a = $this->drv->select('reds')->fields(['name'])->execute();
            $b = $this->drv->select('blues')->fields(['name'])->execute();

            $this->assertNotSame($a, $b);
        }

        /** The regression itself: the first result must survive a later query. */
        #[Test]
        public function anEarlierResultStillHoldsItsOwnRows(): void
        {
            $reds = $this->drv->select('reds')->fields(['name'])->execute();
            $this->drv->select('blues')->fields(['name'])->execute();

            $this->assertSame('red', $reds->fetch()->name);
        }

        /** Order of fetching must not matter either. */
        #[Test]
        public function bothResultsRemainReadableInEitherOrder(): void
        {
            $reds = $this->drv->select('reds')->fields(['name'])->execute();
            $blues = $this->drv->select('blues')->fields(['name'])->execute();

            $this->assertSame('blue', $blues->fetch()->name);
            $this->assertSame('red', $reds->fetch()->name);
        }

        /**
         * The builder can no longer answer for a query at all — the aliasing is gone
         * structurally, not by convention.
         */
        #[Test]
        public function theBuilderCannotFetch(): void
        {
            foreach (['fetch', 'fetchAll', 'fetchAssoc', 'fetchAllAssoc', 'fetchColumn'] as $method) {
                $this->assertFalse(
                    method_exists($this->drv, $method),
                    sprintf('%s() is back on the builder — results must own the rows', $method),
                );
            }
        }

        #[Test]
        public function executeHandsBackAResult(): void
        {
            $this->assertInstanceOf(ResultInterface::class, $this->drv->select('reds')->fields(['*'])->execute());
            $this->assertInstanceOf(ResultInterface::class, $this->drv->executeSql('SELECT 1'));
        }

        #[Test]
        public function rowCountReportsSelectedRows(): void
        {
            $this->drv->executeSql("INSERT INTO reds (id, name) VALUES (2, 'crimson')");

            $this->assertCount(2, $this->drv->select('reds')->fields(['*'])->execute()->fetchAll());
        }
    }
}
