<?php
declare(strict_types=1);

namespace KimaAI\Database;

use wpdb;

defined('ABSPATH') || exit;

final class Db {

  /**
   * Gets the WordPress database instance.
   */
  public static function wpdb(): wpdb {
    global $wpdb;
    return $wpdb;
  }

  /**
   * Builds a prefixed table name.
   *
   * @param string $unprefixed Unprefixed table name.
   */
  public static function tableName(string $unprefixed): string {
    $wpdb = self::wpdb();
    return $wpdb->prefix . $unprefixed;
  }

  /**
   * Checks whether a table exists.
   *
   * @param string $full_table_name Full table name.
   */
  public static function tableExists(string $full_table_name): bool {
    $cache_key = 'table_exists_' . md5($full_table_name);
    $cached = wp_cache_get($cache_key, 'kimaai_db');
    if (is_bool($cached)) {
      return $cached;
    }

    $wpdb = self::wpdb();
    // phpcs:ignore
    $val = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $full_table_name));
    $exists = (string) $val === $full_table_name;
    wp_cache_set($cache_key, $exists, 'kimaai_db', MINUTE_IN_SECONDS);
    return $exists;
  }

  /**
   * Checks whether a column exists.
   *
   * @param string $full_table_name Full table name.
   * @param string $column Column name.
   */
  public static function columnExists(string $full_table_name, string $column): bool {
    $cache_key = 'column_exists_' . md5($full_table_name . ':' . $column);
    $cached = wp_cache_get($cache_key, 'kimaai_db');
    if (is_bool($cached)) {
      return $cached;
    }

    $wpdb = self::wpdb();
    $db = (string) $wpdb->dbname;
    // phpcs:ignore
    $val = $wpdb->get_var(
      $wpdb->prepare(
        'SELECT 1
           FROM INFORMATION_SCHEMA.COLUMNS
          WHERE TABLE_SCHEMA = %s
            AND TABLE_NAME   = %s
            AND COLUMN_NAME  = %s
          LIMIT 1',
        $db,
        $full_table_name,
        $column
      )
    );
    $exists = (bool) $val;
    wp_cache_set($cache_key, $exists, 'kimaai_db', MINUTE_IN_SECONDS);
    return $exists;
  }

  /**
   * Checks whether an index exists.
   *
   * @param string $full_table_name Full table name.
   * @param string $index_name Index name.
   */
  public static function indexExists(string $full_table_name, string $index_name): bool {
    $cache_key = 'index_exists_' . md5($full_table_name . ':' . $index_name);
    $cached = wp_cache_get($cache_key, 'kimaai_db');
    if (is_bool($cached)) {
      return $cached;
    }

    $wpdb = self::wpdb();
    $db = (string) $wpdb->dbname;
    // phpcs:ignore
    $val = $wpdb->get_var(
      $wpdb->prepare(
        'SELECT 1
           FROM INFORMATION_SCHEMA.STATISTICS
          WHERE TABLE_SCHEMA = %s
            AND TABLE_NAME   = %s
            AND INDEX_NAME   = %s
          LIMIT 1',
        $db,
        $full_table_name,
        $index_name
      )
    );
    $exists = (bool) $val;
    wp_cache_set($cache_key, $exists, 'kimaai_db', MINUTE_IN_SECONDS);
    return $exists;
  }

  /**
   * Add a column if it doesn't exist. Safe for forward-compatible upgrades.
   * $column_sql should be like: "new_col VARCHAR(64) NULL DEFAULT NULL"
   */
  public static function addColumnIfMissing(string $full_table_name, string $column, string $column_sql): void {
    if (self::columnExists($full_table_name, $column)) {
      return;
    }
    $wpdb = self::wpdb();
    $sql = sprintf('ALTER TABLE %s ADD COLUMN %s', self::quoteIdentifier($full_table_name), $column_sql);
    call_user_func([$wpdb, 'query'], $sql);
  }

  /**
   * Create an index if missing. $index_sql like: "KEY idx_name (col)" or "UNIQUE KEY uniq (a,b)"
   */
  public static function addIndexIfMissing(string $full_table_name, string $index_name, string $index_sql): void {
    if (self::indexExists($full_table_name, $index_name)) {
      return;
    }
    $wpdb = self::wpdb();
    $sql = sprintf('ALTER TABLE %s ADD %s', self::quoteIdentifier($full_table_name), $index_sql);
    call_user_func([$wpdb, 'query'], $sql);
  }

  /**
   * Quotes a SQL identifier after validating its shape.
   *
   * @param string $identifier Table or column identifier.
   *
   * @throws \InvalidArgumentException If the identifier is invalid.
   */
  public static function quoteIdentifier(string $identifier): string {
    $parts = explode('.', $identifier);
    $quoted = [];

    foreach ($parts as $part) {
      if (!preg_match('/^[A-Za-z0-9_]+$/', $part)) {
        throw new \InvalidArgumentException('Invalid database identifier.');
      }

      $quoted[] = '`' . $part . '`';
    }

    return implode('.', $quoted);
  }
}
