<?php

declare(strict_types=1);

namespace Apexl\Vault\Schemas;

use Apexl\Vault\Exceptions\FormatColumnsException;
use Apexl\Vault\Interfaces\QueryBuilderInterface;
use Apexl\Vault\Interfaces\SchemaInterface;
use stdClass;

final class MysqlSchema implements SchemaInterface
{
    /**
     * Every statement goes through newQuery(), not the builder handed in: DDL sets
     * the sql on whatever builder runs it, and the one behind a vault is shared with
     * the rest of the process. Callers used to have to remember this — the io table
     * description cache wrote `newQuery()->describe(...)` — and forgetting it clobbered
     * an in-flight query.
     */
    public function __construct(private readonly QueryBuilderInterface $driver) {}

    public function describe(string $table): array
    {
        return $this->driver
            ->newQuery()
            ->executeSql(sprintf('DESCRIBE %s', $table))
            ->fetchAll() ?: [];
    }

    /**
     * @throws FormatColumnsException
     */
    public function createTable(string $table, stdClass $colList, bool $existsCheck = true): void
    {
        // ENGINE is MySQL's word, and this is the MySQL schema — it was a caller-facing
        // argument only because the contract was shared with stores that have no engines.
        $this->driver->newQuery()->executeSql(sprintf(
        /** @lang TEXT */
            'CREATE TABLE %s %s (%s) ENGINE=INNODB',
            $existsCheck ? 'IF NOT EXISTS ' : '',
            $table,
            $this->formatColumns($colList, 'CREATE'),
        ));

        foreach ($colList->_indexes ?? [] as $index) {
            $this->driver->newQuery()->executeSql(sprintf(
            /** @lang TEXT */
                'CREATE INDEX %1$s%2$s on %1$s (%2$s)',
                $table,
                $index,
            ));
        }

        foreach ($colList->_unique ?? [] as $index) {
            $this->driver->newQuery()->executeSql(sprintf(
            /** @lang TEXT */
                'CREATE UNIQUE INDEX %1$s%2$s_unique on %1$s (%2$s)',
                $table,
                $index,
            ));
        }
    }

    public function keyIsAutoIncrementing(string $keyField, string $table): bool
    {
        $data = $this->driver
            ->newQuery()
            ->executeSql(sprintf(
                "SELECT COUNT(*) count FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE TABLE_NAME = '%s'
                        AND COLUMN_NAME = '%s'
                        AND EXTRA like '%%auto_increment%%'
                   ",
                $table,
                $keyField,
            ))
            ->fetch();

        return ((int) $data->count) !== 0;
    }

    /**
     * @throws FormatColumnsException
     * @noinspection PhpSameParameterValueInspection
     */
    private function formatColumns($tableData, string $operation): string
    {
        if (!isset($tableData->_columns)) {
            throw new FormatColumnsException("MYSQL Table definition must contain columns");
        }
        $noCols = count($tableData->_columns);
        $columns = $tableData->_columns;
        $formattedColumns = [];
        for ($i = 0; $i < $noCols; ++$i) {
            if (!isset($columns[$i]->name)) {
                throw new FormatColumnsException(
                    sprintf('MYSQL Column name missing from column operation .%s', $operation)
                );
            }
            if (!isset($columns[$i]->type) && $operation == "CREATE") {
                throw new FormatColumnsException(
                    sprintf('MYSQL Column type missing from column %s operation', $operation)
                );
            }
            $formattedColumns[$i] = $this->buildColumnDefinition($columns[$i], $tableData->primary_key);
        }

        return implode(', ', $formattedColumns);
    }

    private function buildColumnDefinition(stdClass $column, ?string $primaryKey = null): string
    {
        $definition = $column->name;
        //build based on type.
        switch (strtolower($column->type)) {
            case 'varchar':
                $length = $column->length ?? 255;
                $definition .= ' varchar(' . $length . ')';
                break;
            case 'bool':
            case 'boolean':
                $definition .= ' tinyint(1)';
                break;
            case 'decimal':
                $length = $column->length ?? 11;
                $decimalPlaces = $column->decimal_places ?? 2;
                $definition .= " DECIMAL($length,$decimalPlaces)";
                break;
            case 'int':
                $length = $column->length ?? 11;
                $definition .= ' int(' . $length . ')';
                break;
            case 'json':
                $definition .= ' JSON';
                break;
            default:
                $definition .= ' ' . strtolower((string) $column->type);
                break;
        }

        if (isset($column->auto_increment) && $column->auto_increment) {
            $definition .= ' auto_increment';
        } else {
            if (isset($column->default)) {
                $definition .= ' default ' . (is_string(
                        $column->default
                    ) ? "'" . $column->default . "'" : $column->default);
            }
            if (strtolower($column->type) != 'json') {
                if (isset($column->is_null) && $column->is_null) {
                    $definition .= ' null';
                } else {
                    //assume not nulls
                    $definition .= ' not null';
                }
            }
        }

        if ($primaryKey && $primaryKey == $column->name) {
            $definition .= ' primary key';
        }

        //build row
        return $definition;
    }
}
