<?php

namespace classes;

/**
 * Ensures additive schema for orders metadata.
 */
class orders_schema_bootstrap
{
    private static bool $initialized = false;

    public static function ensureTables(): void
    {
        if (self::$initialized) {
            return;
        }

        global $db;

        if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
            return;
        }

        if (!self::tableExists($db, 'orders')) {
            return;
        }

        if (!self::columnExists($db, 'orders', 'include_in_invoice')) {
            $db->query(
                "ALTER TABLE orders
                    ADD COLUMN include_in_invoice TINYINT(1) NULL DEFAULT NULL
                    AFTER created_at"
            );
        }

        if (!self::columnExists($db, 'orders', 'safety_seal')) {
            $db->query(
                "ALTER TABLE orders
                    ADD COLUMN safety_seal VARCHAR(255) NULL DEFAULT NULL
                    AFTER po"
            );
        }

        self::backfillBookingPoDefaults($db);

        self::ensureIndex($db, 'orders', 'idx_orders_period_customer_created_deleted', 'customer_id, created_at, deleted_at');
        self::ensureIndex($db, 'orders', 'idx_orders_period_created_deleted_customer', 'created_at, deleted_at, customer_id');
        self::ensureIndex($db, 'order_items', 'idx_order_items_order_deleted', 'order_id, deleted_at');
        self::ensureIndex($db, 'customer_attributes', 'idx_customer_attributes_attribute_user', 'attribute, user_id');

        self::$initialized = true;
    }

    private static function backfillBookingPoDefaults(object $db): void
    {
        if (
            !self::tableExists($db, 'order_bookings')
            || !self::columnExists($db, 'orders', 'booking_id')
            || !self::columnExists($db, 'orders', 'po')
            || !self::columnExists($db, 'order_bookings', 'po')
            || !self::columnExists($db, 'order_bookings', 'customer_number')
            || !self::columnExists($db, 'order_bookings', 'department')
            || !self::columnExists($db, 'order_bookings', 'deleted_at')
        ) {
            return;
        }

        $db->query(
            "UPDATE orders o
                INNER JOIN order_bookings b ON b.id = o.booking_id
                    AND b.customer_number = o.customer_id
                    AND b.department = o.department_id
                    AND b.deleted_at IS NULL
                SET o.po = b.po
                WHERE o.booking_id IS NOT NULL
                    AND o.booking_id > 0
                    AND (o.po IS NULL OR TRIM(o.po) = '')
                    AND b.po IS NOT NULL
                    AND TRIM(b.po) <> ''"
        );
    }

    private static function tableExists(object $db, string $table): bool
    {
        $table = self::escapeIdentifier($table);
        $result = $db->query("SHOW TABLES LIKE '{$table}'");

        if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
            return false;
        }

        return (int)$result->num_rows > 0;
    }

    private static function columnExists(object $db, string $table, string $column): bool
    {
        $table = self::escapeIdentifier($table);
        $column = self::escapeIdentifier($column);
        $result = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");

        if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
            return false;
        }

        return (int)$result->num_rows > 0;
    }

    private static function ensureIndex(object $db, string $table, string $index, string $columns): void
    {
        if (
            !self::tableExists($db, $table)
            || self::indexExists($db, $table, $index)
            || !self::columnsExist($db, $table, $columns)
        ) {
            return;
        }

        $table = self::escapeIdentifier($table);
        $index = self::escapeIdentifier($index);
        $db->query("ALTER TABLE `{$table}` ADD INDEX `{$index}` ({$columns})");
    }

    private static function columnsExist(object $db, string $table, string $columns): bool
    {
        foreach (explode(',', $columns) as $column) {
            $column = trim($column, " \t\n\r\0\x0B`");
            if ($column === '' || !self::columnExists($db, $table, $column)) {
                return false;
            }
        }

        return true;
    }

    private static function indexExists(object $db, string $table, string $index): bool
    {
        $table = self::escapeIdentifier($table);
        $index = self::escapeIdentifier($index);
        $result = $db->query("SHOW INDEX FROM `{$table}` WHERE Key_name = '{$index}'");

        if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
            return false;
        }

        return (int)$result->num_rows > 0;
    }

    private static function escapeIdentifier(string $value): string
    {
        return str_replace(['\\', "'", '`'], ['\\\\', "\\'", ''], $value);
    }
}
