<?php

declare(strict_types=1);

namespace XBotControl;

use Clue\React\SQLite\DatabaseInterface;
use React\Promise\PromiseInterface;
use Clue\React\SQLite\Result;

class Storage
{
    protected static $instance;
    /** @var DatabaseInterface $db */
    public $db;
    public $cache = [];

    protected static $tablesCache = [
        'ip',
        'domain',
        'useragent',
        'headers',
        'path'
    ];

    public function __construct()
    {
        $this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/requests.db');

        foreach (self::$tablesCache as $cacheParition) {
            $this->cache[$cacheParition] = new \React\Cache\ArrayCache(1000);
        }
    }

    public static function getInstance(): Storage
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public static function getId(string $cacheParition, string $key): PromiseInterface
    {
        $storage = self::getInstance();

        /*         if (!isset(self::$tablesCache[$cacheParition])) {
            return $storage::insert($cacheParition, ['data' => $key])->then(function ($result) {
                return $result->rows["0"][$result->columns['0']];
            });
        } */


        return $storage->cache[$cacheParition]->get($key)
            ->then(function ($result) use ($storage, $cacheParition, $key) {
                if ($result === null) {
                    return self::insertAndCache($storage, $cacheParition, $key);
                }
                return (int) $result;
            }, function () {
                return 0;
            });
    }


    private static function insertAndCache(Storage $storage, string $cacheParition, string $key): PromiseInterface
    {

        $query =  "INSERT INTO $cacheParition (data) VALUES (?) ON CONFLICT(data) DO UPDATE SET data=? RETURNING rowid ;";

        return $storage->db
            ->query($query, [$key, $key])
            ->then(function (Result $result) use ($storage, $cacheParition, $key) {

                return self::cache($storage, $cacheParition, $key, $result);
            });
    }

    private static function cache(Storage $storage, string $cacheParition, string $key, Result $result): PromiseInterface
    {
        $value = $result->rows["0"][$result->columns['0']];
        return $storage->cache[$cacheParition]->set($key, $value)
            ->then(function () use ($value) {
                return $value;
            });
    }

    public static function insert(string $table, array $values): PromiseInterface
    {
        $columns = implode(", ", array_keys($values));
        $placeholders = implode(", ", array_fill(0, count($values), "?"));
        $query = sprintf("INSERT INTO %s (%s) VALUES (%s);", $table, $columns, $placeholders);
        $params = array_values($values);

        $storage = self::getInstance();
        return $storage->db->query($query, $params)->then(function (Result $result) {
            return $result->insertId;
        });
    }
}
