<?php

namespace classes;

/**
 * Ensures additive self-serve tables and columns exist.
 *
 * This project has no centralized migration runner, so the bootstrap must be
 * idempotent and safe to call from runtime flows.
 */
class selfserve_schema_bootstrap
{
    private static bool $initialized = false;

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

        global $db;

        $queries = [
            "CREATE TABLE IF NOT EXISTS selfserve_config_versions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                department_id INT NOT NULL,
                status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
                version_number INT NOT NULL,
                config_json JSON NOT NULL,
                validation_result_json JSON NULL,
                source_version_id INT NULL,
                created_by INT NULL,
                published_at DATETIME NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                INDEX idx_selfserve_config_versions_department_status (department_id, status),
                INDEX idx_selfserve_config_versions_department_version (department_id, version_number)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS selfserve_machine_types (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                description VARCHAR(255) NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                UNIQUE KEY uniq_selfserve_machine_types_name (name)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS selfserve_wash_sessions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                lane_id INT NOT NULL,
                department_id INT NOT NULL,
                machine_type_id INT NULL,
                customer_number INT NULL,
                subuser_id INT NULL,
                vehicle_id INT NULL,
                vehicle_type_id INT NULL,
                reg VARCHAR(255) NOT NULL,
                status VARCHAR(64) NOT NULL DEFAULT 'PENDING_QUESTIONS',
                allowed TINYINT(1) NOT NULL DEFAULT 0,
                machine_relay_enabled TINYINT(1) NOT NULL DEFAULT 0,
                machine_relay_enabled_at DATETIME NULL,
                machine_start_triggered TINYINT(1) NOT NULL DEFAULT 0,
                machine_start_triggered_at DATETIME NULL,
                wash_started_at DATETIME NULL,
                order_id INT NULL,
                completed_at DATETIME NULL,
                metadata_json JSON NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                INDEX idx_selfserve_wash_sessions_lane_reg (lane_id, reg),
                INDEX idx_selfserve_wash_sessions_status (status),
                INDEX idx_selfserve_wash_sessions_customer (customer_number),
                INDEX idx_selfserve_wash_sessions_subuser_active (subuser_id, completed_at),
                INDEX idx_selfserve_wash_sessions_customer_subuser_active (customer_number, subuser_id, completed_at),
                INDEX idx_selfserve_wash_sessions_created_at (created_at),
                INDEX idx_selfserve_wash_sessions_department_completed (department_id, completed_at),
                INDEX idx_selfserve_wash_sessions_order (order_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS selfserve_wash_session_answers (
                id INT AUTO_INCREMENT PRIMARY KEY,
                session_id INT NOT NULL,
                question_id INT NOT NULL,
                question_text VARCHAR(255) NOT NULL,
                answer_value TINYINT(1) NOT NULL,
                answered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                UNIQUE KEY uniq_selfserve_wash_session_answer (session_id, question_id),
                INDEX idx_selfserve_wash_session_answers_session (session_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS selfserve_wash_session_tasks (
                id INT AUTO_INCREMENT PRIMARY KEY,
                session_id INT NOT NULL,
                task_id INT NULL,
                task_text VARCHAR(255) NOT NULL,
                description TEXT NULL,
                services JSON NULL,
                buttons JSON NULL,
                dynamic_image_id INT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                INDEX idx_selfserve_wash_session_tasks_session (session_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS selfserve_wash_session_events (
                id INT AUTO_INCREMENT PRIMARY KEY,
                session_id INT NOT NULL,
                event_type VARCHAR(64) NOT NULL,
                payload_json JSON NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_selfserve_wash_session_events_session (session_id),
                INDEX idx_selfserve_wash_session_events_type (event_type)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS department_selfserve_studio_layouts (
                id INT AUTO_INCREMENT PRIMARY KEY,
                department_id INT NOT NULL,
                user_id INT NULL,
                layout_json JSON NOT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                INDEX idx_department_selfserve_studio_layouts_department_user (department_id, user_id),
                INDEX idx_department_selfserve_studio_layouts_department_updated (department_id, updated_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS department_selfserve_studio_virtual_hardware (
                id INT AUTO_INCREMENT PRIMARY KEY,
                department_id INT NOT NULL,
                config_json JSON NOT NULL,
                created_by INT NULL,
                updated_by INT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                UNIQUE KEY uniq_selfserve_vhw_department (department_id),
                INDEX idx_selfserve_vhw_dept_updated (department_id, updated_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            "CREATE TABLE IF NOT EXISTS department_selfserve_path_confirmations (
                id INT AUTO_INCREMENT PRIMARY KEY,
                department_id INT NOT NULL,
                lane_id INT NULL,
                vehicle_type_id INT NULL,
                config_version_id INT NULL,
                config_source VARCHAR(32) NOT NULL DEFAULT 'draft',
                path_signature VARCHAR(128) NOT NULL,
                result_signature VARCHAR(128) NOT NULL,
                answers_json JSON NOT NULL,
                result_json JSON NOT NULL,
                scope_json JSON NULL,
                confirmed_by INT NULL,
                confirmed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                stale_reason VARCHAR(255) NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                deleted_at TIMESTAMP NULL DEFAULT NULL,
                INDEX idx_selfserve_path_conf_department_scope (department_id, lane_id, vehicle_type_id, config_version_id),
                INDEX idx_selfserve_path_conf_signature (department_id, config_version_id, path_signature)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
        ];

        foreach ($queries as $sql) {
            $db->query($sql);
        }

        self::ensureColumn(
            'department_lanes',
            'machine_type_id',
            'ALTER TABLE department_lanes ADD COLUMN machine_type_id INT NULL AFTER dynamic_image_id'
        );
        self::ensureColumn(
            'department_lanes',
            'selfserve_enabled',
            'ALTER TABLE department_lanes ADD COLUMN selfserve_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER machine_type_id'
        );
        self::ensureColumn(
            'department_selfserve_conditions',
            'machine_type_id',
            'ALTER TABLE department_selfserve_conditions ADD COLUMN machine_type_id INT NULL AFTER product'
        );
        self::ensureColumn(
            'department_selfserve_tasks',
            'machine_type_id',
            'ALTER TABLE department_selfserve_tasks ADD COLUMN machine_type_id INT NULL AFTER product'
        );
        self::ensureColumn(
            'department_selfserve_tasks',
            'gate_type',
            "ALTER TABLE department_selfserve_tasks ADD COLUMN gate_type VARCHAR(16) NULL DEFAULT 'ALWAYS' AFTER condition_id"
        );
        self::ensureColumn(
            'department_selfserve_tasks',
            'gate_ref_id',
            'ALTER TABLE department_selfserve_tasks ADD COLUMN gate_ref_id INT NULL AFTER gate_type'
        );
        self::ensureColumnDataType(
            'department_selfserve_tasks',
            'description',
            ['text', 'mediumtext', 'longtext'],
            'ALTER TABLE department_selfserve_tasks MODIFY COLUMN description TEXT NULL AFTER task'
        );
        self::ensureColumnDataType(
            'selfserve_wash_session_tasks',
            'description',
            ['text', 'mediumtext', 'longtext'],
            'ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text'
        );
        self::ensureColumn(
            'selfserve_wash_session_tasks',
            'dynamic_images_vehicle_type',
            'ALTER TABLE selfserve_wash_session_tasks ADD COLUMN dynamic_images_vehicle_type INT NULL AFTER buttons'
        );
        self::ensureColumn(
            'selfserve_wash_sessions',
            'customer_number',
            'ALTER TABLE selfserve_wash_sessions ADD COLUMN customer_number INT NULL AFTER department_id'
        );
        self::ensureColumn(
            'selfserve_wash_sessions',
            'subuser_id',
            'ALTER TABLE selfserve_wash_sessions ADD COLUMN subuser_id INT NULL AFTER customer_number'
        );
        self::ensureColumn(
            'selfserve_wash_sessions',
            'wash_started_at',
            'ALTER TABLE selfserve_wash_sessions ADD COLUMN wash_started_at DATETIME NULL AFTER machine_start_triggered_at'
        );
        self::ensureIndex(
            'selfserve_wash_sessions',
            'idx_selfserve_wash_sessions_customer',
            'ALTER TABLE selfserve_wash_sessions ADD INDEX idx_selfserve_wash_sessions_customer (customer_number)'
        );
        self::ensureIndex(
            'selfserve_wash_sessions',
            'idx_selfserve_wash_sessions_subuser_active',
            'ALTER TABLE selfserve_wash_sessions ADD INDEX idx_selfserve_wash_sessions_subuser_active (subuser_id, completed_at)'
        );
        self::ensureIndex(
            'selfserve_wash_sessions',
            'idx_selfserve_wash_sessions_customer_subuser_active',
            'ALTER TABLE selfserve_wash_sessions ADD INDEX idx_selfserve_wash_sessions_customer_subuser_active (customer_number, subuser_id, completed_at)'
        );
        self::ensureIndex(
            'selfserve_wash_sessions',
            'idx_selfserve_wash_sessions_department_completed',
            'ALTER TABLE selfserve_wash_sessions ADD INDEX idx_selfserve_wash_sessions_department_completed (department_id, completed_at)'
        );
        self::ensureIndex(
            'selfserve_wash_sessions',
            'idx_selfserve_wash_sessions_order',
            'ALTER TABLE selfserve_wash_sessions ADD INDEX idx_selfserve_wash_sessions_order (order_id)'
        );

        self::$initialized = true;
    }

    public static function tableHasColumn(string $table, string $column): bool
    {
        global $db;
        $table = $db->escape_string($table);
        $column = $db->escape_string($column);
        $database = $db->escape_string($db->getDatabase());

        $sql = "SELECT COUNT(*) AS c
                FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = '$database'
                  AND TABLE_NAME = '$table'
                  AND COLUMN_NAME = '$column'";
        $result = $db->query($sql);
        if (!$result) {
            return false;
        }
        $row = $result->fetch_assoc();
        return ((int)($row['c'] ?? 0)) > 0;
    }

    public static function ensureColumn(string $table, string $column, string $alterSql): void
    {
        global $db;
        if (self::tableHasColumn($table, $column)) {
            return;
        }
        $db->query($alterSql);
    }

    public static function tableHasIndex(string $table, string $index): bool
    {
        global $db;
        $table = $db->escape_string($table);
        $index = $db->escape_string($index);
        $database = $db->escape_string($db->getDatabase());

        $sql = "SELECT COUNT(*) AS c
                FROM information_schema.STATISTICS
                WHERE TABLE_SCHEMA = '$database'
                  AND TABLE_NAME = '$table'
                  AND INDEX_NAME = '$index'";
        $result = $db->query($sql);
        if (!$result) {
            return false;
        }
        $row = $result->fetch_assoc();
        return ((int)($row['c'] ?? 0)) > 0;
    }

    public static function ensureIndex(string $table, string $index, string $alterSql): void
    {
        global $db;
        if (self::tableHasIndex($table, $index)) {
            return;
        }
        $db->query($alterSql);
    }

    /**
     * @param array<int,string> $acceptedDataTypes
     */
    public static function ensureColumnDataType(string $table, string $column, array $acceptedDataTypes, string $alterSql): void
    {
        global $db;

        $columnInfo = self::columnInfo($table, $column);
        if ($columnInfo === null) {
            return;
        }

        $dataType = strtolower((string)($columnInfo['DATA_TYPE'] ?? ''));
        $acceptedDataTypes = array_map(static fn(string $type): string => strtolower($type), $acceptedDataTypes);
        if (in_array($dataType, $acceptedDataTypes, true)) {
            return;
        }

        $db->query($alterSql);
    }

    /**
     * @return array<string,mixed>|null
     */
    public static function columnInfo(string $table, string $column): ?array
    {
        global $db;
        $table = $db->escape_string($table);
        $column = $db->escape_string($column);
        $database = $db->escape_string($db->getDatabase());

        $sql = "SELECT DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH
                FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = '$database'
                  AND TABLE_NAME = '$table'
                  AND COLUMN_NAME = '$column'
                LIMIT 1";
        $result = $db->query($sql);
        if (!$result) {
            return null;
        }
        $row = $result->fetch_assoc();
        return is_array($row) ? $row : null;
    }
}
