<?php

namespace Plainwork\Database;

use Plainwork\Core\App;
use Plainwork\Database\Database;
use Plainwork\Database\Exception\DatabaseException;

class Migrator
{
    public const TABLE_NAME = '__migration'; 

    public $db = Database::class;
    public $app = App::class;
    private array $migrations = [];
    private ?array $versions = null;

    public function __construct()
    {
// echo 'AXAX: ' . __METHOD__ . '<br>';
        $this->registerMigration('migration', 1, [get_class($this), 'up1'], [get_class($this), 'down1']);
    }

    private function table(): string
    {
        return '__migration';
    }

    public function up1()
    {
        $cols = [
            'module' => ['type' => 'VARCHAR(32)', 'null' => false, 'primaryKey' => true],
            'version' => ['type' => 'INTEGER', 'null' => false]
        ];
        $this->db->createTable(static::TABLE_NAME, $cols);
    }

    public function down1()
    {
        $this->db->dropTable(static::TABLE_NAME);
    }

    // public function up2()
    // {
        // $this->db
            // ->table($this->getDatabaseTable())
            // ->field('title')
            // ->addIndex()
            // ;
    // }


    public function isInstalled()
    {
        $myVersion = $this->getCurrentVersion('migration');
        $ret = $myVersion ? true : false;

        return $ret;
    }

    private function getCurrentVersion(string $module): int
    {
        $versions = $this->getCurrentVersions();

        return $versions[$module] ?? 0;
    }

    public function registerMigration(string $module, int $version, $funcUp, $funcDown = null): self
    {
        return $this->register($module, $version, $funcUp, $funcDown);
    }

    public function register(string $module, int $version, $funcUp, $funcDown = null): self
    {
        if (!isset($this->migrations[$version])) {
            $this->migrations[$version] = [];
        }

        $this->migrations[$version][$module] = [
            'module' => $module,
            'version' => $version,
            'up' => $funcUp,
            'down' => $funcDown,
        ];

        return $this;
    }

    public function up()
    {
        ksort($this->migrations);

        $run = [];
        foreach ($this->migrations as $version => $migrations) {
            foreach ($migrations as $migration) {
                $module = $migration['module'];
                $currentVersion = $this->getCurrentVersion($module);
                if ($currentVersion >= $version) {
                    continue;
                }

                $this->app->call($migration['up'], $this->db);
                $this->setCurrentVersion($module, $version);
            }
        }

        return true;
    }

    public function down()
    {
        $migrations = $this->migrations[1] ?? [];

        $thisDown = $migrations['migration']['down'] ?? null;
        unset($migrations['migration']);

        foreach ($migrations as $migration) {
            if (!$migration['down']) {
                continue;
            }

            $this->app->call($migration['down'], $this->db);
        }

        if ($thisDown) {
            $this->app->call($thisDown);
        }

        return true;
    }

    private function getCurrentVersions(): array
    {
        if (null !== $this->versions) {
            return $this->versions;
        }

        $this->versions = [];

        try {
            $sql = 'SELECT `module`, `version` FROM `' . static::TABLE_NAME . '`';
            $res = $this->db->execute($sql, []);

            if ($res) {
                foreach ($res as $a) {
                    $module = $a['module'];
                    $version = $a['version'];
                    $this->versions[$module] = $version;
                }
            }
        } catch (\Exception $e) {
        }

        return $this->versions;
    }

    private function setCurrentVersion(string $module, int $version)
    {
        $table = static::TABLE_NAME;
        $q = $this->db->q();

        if ($version < 1) {
            $q->deleteFrom($table);
            $q->whereString('module', '=', $module);
            $q->execute();
        } elseif ($version > 1) {
            $q->update($table);
            $q->valueInt('version', $version);
            $q->whereString('module', '=', $module);
            $q->execute();
        } else {
            $q->insertInto($table);
            $q->valueString('module', $module);
            $q->valueInt('version', $version);
            $q->execute();
        }
        $this->versions[$module] = $version;

        return $this;
    }
}
