<?php
namespace nur\sery\db\sqlite;

use nulib\tests\TestCase;
use nur\sery\cl;
use nur\sery\db\Capacitor;
use nur\sery\db\CapacitorChannel;

class SqliteStorageTest extends TestCase {
  function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
    $storage->reset($channel);
    $storage->charge($channel, "first");
    $storage->charge($channel, "second");
    $storage->charge($channel, "third");
    $items = cl::all($storage->discharge($channel, false));
    self::assertSame(["first", "second", "third"], $items);
  }

  function _testChargeArrays(SqliteStorage $storage, ?string $channel) {
    $storage->reset($channel);
    $storage->charge($channel, ["id" => 10, "name" => "first"]);
    $storage->charge($channel, ["name" => "second", "id" => 20]);
    $storage->charge($channel, ["name" => "third", "id" => "30"]);
  }

  function testChargeStrings() {
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $this->_testChargeStrings($storage, null);
    $storage->close();
  }

  function testChargeArrays() {
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $storage->addChannel(new class extends CapacitorChannel {
      const NAME = "arrays";
      const COLUMN_DEFINITIONS = ["id" => "integer"];

      function getItemValues($item): ?array {
        return ["id" => $item["id"] ?? null];
      }
    });

    $this->_testChargeStrings($storage, "strings");
    $this->_testChargeArrays($storage, "arrays");
    $storage->close();
  }

  function testEach() {
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $capacitor = new Capacitor($storage, new class extends CapacitorChannel {
      const NAME = "each";
      const COLUMN_DEFINITIONS = [
        "age" => "integer",
        "done" => "integer default 0",
      ];

      function getItemValues($item): ?array {
        return [
          "age" => $item["age"],
        ];
      }
    });

    $capacitor->reset();
    $capacitor->charge(["name" => "first", "age" => 5]);
    $capacitor->charge(["name" => "second", "age" => 10]);
    $capacitor->charge(["name" => "third", "age" => 15]);
    $capacitor->charge(["name" => "fourth", "age" => 20]);

    $setDone = function ($item, $row, $suffix=null) {
      $updates = ["done" => 1];
      if ($suffix !== null) {
        $item["name"] .= $suffix;
        $updates["item"] = $item;
      }
      return $updates;
    };
    $capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
    $capacitor->each(["done" => 0], $setDone, null);

    Txx(cl::all($capacitor->discharge(false)));
    $capacitor->close();
    self::assertTrue(true);
  }

  function testPrimayKey() {
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $capacitor = new Capacitor($storage, new class extends CapacitorChannel {
      const NAME = "pk";
      const COLUMN_DEFINITIONS = [
        "id_" => "varchar primary key",
        "done" => "integer default 0",
      ];

      function getItemValues($item): ?array {
        return [
          "id_" => $item["numero"],
        ];
      }
    });

    $capacitor->charge(["numero" => "a", "name" => "first", "age" => 5]);
    $capacitor->charge(["numero" => "b", "name" => "second", "age" => 10]);
    $capacitor->charge(["numero" => "c", "name" => "third", "age" => 15]);
    $capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 20]);
    sleep(2);
    $capacitor->charge(["numero" => "b", "name" => "second", "age" => 100]);
    $capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 200]);

    $capacitor->close();
    self::assertTrue(true);
  }

  function testSum() {
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $capacitor = new Capacitor($storage, new class extends CapacitorChannel {
      const NAME = "sum";
      const COLUMN_DEFINITIONS = [
        "a__" => "varchar",
        "b__" => "varchar",
        "b__sum_" => self::SUM_DEFINITION,
      ];

      function getItemValues($item): ?array {
        return [
          "a" => $item["a"],
          "b" => $item["b"],
        ];
      }
    });

    $capacitor->reset();
    $capacitor->charge(["a" => null, "b" => null]);
    $capacitor->charge(["a" => "first", "b" => "second"]);

    Txx("=== all");
    /** @var Sqlite $sqlite */
    $sqlite = $capacitor->getStorage()->db();
    Txx(cl::all($sqlite->all([
      "select",
      "from" => $capacitor->getChannel()->getTableName(),
    ])));
    Txx("=== each");
    $capacitor->each(null, function ($item, $values) {
      Txx($values);
    });

    $capacitor->close();
    self::assertTrue(true);
  }

  function testEachValues() {
    # tester que values contient bien toutes les valeurs de la ligne
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $capacitor = new Capacitor($storage, new class extends CapacitorChannel {
      const NAME = "each_values";
      const COLUMN_DEFINITIONS = [
        "name" => "varchar primary key",
        "age" => "integer",
        "done" => "integer default 0",
        "notes" => "text",
      ];

      function getItemValues($item): ?array {
        return [
          "name" => $item["name"],
          "age" => $item["age"],
        ];
      }
    });

    $capacitor->reset();
    $capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $values, ?array $pvalues) {
      self::assertSame("first", $item["name"]);
      self::assertSame(5, $item["age"]);
      self::assertnotnull($values);
      self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($values));
      self::assertSame([
        "name" => "first",
        "age" => 5,
        "item" => $item,
      ], cl::select($values, ["name", "age", "item"]));
      self::assertNull($pvalues);
    });
    $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
      self::assertSame("first", $item["name"]);
      self::assertSame(10, $item["age"]);
      self::assertnotnull($values);
      self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
      self::assertSame([
        "name" => "first",
        "age" => 10,
        "done" => 0,
        "notes" => null,
        "item" => $item,
      ], cl::select($values, ["name", "age", "done", "notes", "item"]));
      self::assertNotNull($pvalues);
      self::assertSame([
        "name" => "first",
        "age" => 5,
        "done" => 0,
        "notes" => null,
        "item" => ["name" => "first", "age" => 5],
      ], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
    });

    $capacitor->each(null, function($item, ?array $values) {
      self::assertSame("first", $item["name"]);
      self::assertSame(10, $item["age"]);
      self::assertnotnull($values);
      self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
      self::assertSame([
        "name" => "first",
        "age" => 10,
        "done" => 0,
        "notes" => null,
        "item" => $item,
      ], cl::select($values, ["name", "age", "done", "notes", "item"]));
      return [
        "done" => 1,
        "notes" => "modified",
      ];
    });
    $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
      self::assertSame("first", $item["name"]);
      self::assertSame(10, $item["age"]);
      self::assertnotnull($values);
      self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
      self::assertSame([
        "name" => "first",
        "age" => 10,
        "done" => 1,
        "notes" => "modified",
        "item" => $item,
      ], cl::select($values, ["name", "age", "done", "notes", "item"]));
      self::assertNotNull($pvalues);
      self::assertSame([
        "name" => "first",
        "age" => 10,
        "done" => 1,
        "notes" => "modified",
        "item" => $item,
      ], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
    });

    $capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $values, ?array $pvalues) {
      self::assertSame("first", $item["name"]);
      self::assertSame(20, $item["age"]);
      self::assertnotnull($values);
      self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
      self::assertSame([
        "name" => "first",
        "age" => 20,
        "done" => 1,
        "notes" => "modified",
        "item" => $item,
      ], cl::select($values, ["name", "age", "done", "notes", "item"]));
      self::assertNotNull($pvalues);
      self::assertSame([
        "name" => "first",
        "age" => 10,
        "done" => 1,
        "notes" => "modified",
        "item" => ["name" => "first", "age" => 10],
      ], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
    });
  }

  function testSetItemNull() {
    # tester le forçage de $îtem à null pour économiser la place
    $storage = new SqliteStorage(__DIR__.'/capacitor.db');
    $capacitor = new Capacitor($storage, new class extends CapacitorChannel {
      const NAME = "set_item_null";
      const COLUMN_DEFINITIONS = [
        "name" => "varchar primary key",
        "age" => "integer",
        "done" => "integer default 0",
        "notes" => "text",
      ];

      function getItemValues($item): ?array {
        return [
          "name" => $item["name"],
          "age" => $item["age"],
        ];
      }
    });

    $capacitor->reset();
    $nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $values, ?array $pvalues) {
      self::assertSame([
        "name" => "first", "age" => 5,
        "item" => $item,
      ], cl::select($values, ["name", "age", "item"]));
      return ["item" => null];
    });
    self::assertSame(1, $nbModified);
    sleep(1);
    # nb: on met des sleep() pour que la date de modification soit systématiquement différente

    $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
      self::assertSame([
        "name" => "first", "age" => 10,
        "item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
      ], cl::select($values, ["name", "age", "item", "item__sum_"]));
      self::assertSame([
        "name" => "first", "age" => 5,
        "item" => null, "item__sum_" => null,
      ], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
      return ["item" => null];
    });
    self::assertSame(1, $nbModified);
    sleep(1);

    # pas de modification ici
    $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
      self::assertSame([
        "name" => "first", "age" => 10,
        "item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
      ], cl::select($values, ["name", "age", "item", "item__sum_"]));
      self::assertSame([
        "name" => "first", "age" => 10,
        "item" => null, "item__sum_" => null,
      ], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
      return ["item" => null];
    });
    self::assertSame(0, $nbModified);
    sleep(1);

    $nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $values, ?array $pvalues) {
      self::assertSame([
        "name" => "first", "age" => 20,
        "item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
      ], cl::select($values, ["name", "age", "item", "item__sum_"]));
      self::assertSame([
        "name" => "first", "age" => 10,
        "item" => null, "item__sum_" => null,
      ], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
      return ["item" => null];
    });
    self::assertSame(1, $nbModified);
    sleep(1);
  }
}
