<?php

if (!defined('SQLSRV_FETCH_ASSOC')) {
    define('SQLSRV_FETCH_ASSOC', 2);
}

if (!defined('SQLSRV_FETCH_NUMERIC')) {
    define('SQLSRV_FETCH_NUMERIC', 1);
}

final class MysqlSqlsrvConnection
{
    public PDO $pdo;
    public ?array $lastError = null;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
}

final class MysqlSqlsrvStatement
{
    /** @var array<int, array<string, mixed>> */
    public array $rows;
    public int $index = 0;
    public int $affectedRows = 0;

    /**
     * @param array<int, array<string, mixed>> $rows
     */
    public function __construct(array $rows = [], int $affectedRows = 0)
    {
        $this->rows = $rows;
        $this->affectedRows = $affectedRows;
    }
}

function sqlsrv_connect($serverName, array $connectionOptions = [])
{
    $database = getenv('DB_NAME') ?: ($connectionOptions['Database'] ?? '');
    $user = getenv('DB_USER') ?: ($connectionOptions['Uid'] ?? '');
    $password = getenv('DB_PASS') ?: ($connectionOptions['PWD'] ?? '');
    $host = getenv('DB_HOST') ?: (string)$serverName;
    $port = getenv('DB_PORT') ?: '3306';

    try {
        $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $database);
        $pdo = new PDO($dsn, $user, $password, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => true,
        ]);

        return new MysqlSqlsrvConnection($pdo);
    } catch (Throwable $e) {
        $GLOBALS['__sqlsrv_mysql_last_error'] = [[
            'SQLSTATE' => $e->getCode(),
            'message' => $e->getMessage(),
        ]];
        return false;
    }
}

function sqlsrv_query($conn, string $sql, array $params = [], array $options = [])
{
    if (!$conn instanceof MysqlSqlsrvConnection) {
        $GLOBALS['__sqlsrv_mysql_last_error'] = [[
            'SQLSTATE' => 'HY000',
            'message' => 'Invalid MySQL compatibility connection.',
        ]];
        return false;
    }

    $translatedSql = sqlsrv_mysql_translate_query($sql);

    try {
        $stmt = $conn->pdo->prepare($translatedSql);
        $stmt->execute(sqlsrv_mysql_normalize_params($translatedSql, $params));

        if ($stmt->columnCount() > 0) {
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            foreach ($rows as &$row) {
                $row = sqlsrv_mysql_normalize_row($row);
            }
            unset($row);

            return new MysqlSqlsrvStatement($rows, $stmt->rowCount());
        }

        return new MysqlSqlsrvStatement([], $stmt->rowCount());
    } catch (Throwable $e) {
        $conn->lastError = [[
            'SQLSTATE' => $e->getCode(),
            'message' => $e->getMessage(),
            'sql' => $translatedSql,
        ]];
        $GLOBALS['__sqlsrv_mysql_last_error'] = $conn->lastError;
        return false;
    }
}

function sqlsrv_fetch_array($stmt, int $fetchType = SQLSRV_FETCH_ASSOC)
{
    if (!$stmt instanceof MysqlSqlsrvStatement) {
        return null;
    }

    if (!array_key_exists($stmt->index, $stmt->rows)) {
        return null;
    }

    $row = $stmt->rows[$stmt->index++];

    if ($fetchType === SQLSRV_FETCH_NUMERIC) {
        return array_values($row);
    }

    return $row;
}

function sqlsrv_num_rows($stmt): int
{
    if (!$stmt instanceof MysqlSqlsrvStatement) {
        return 0;
    }

    return count($stmt->rows);
}

function sqlsrv_errors()
{
    return $GLOBALS['__sqlsrv_mysql_last_error'] ?? null;
}

function sqlsrv_close($conn): bool
{
    return true;
}

function sqlsrv_begin_transaction($conn): bool
{
    return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->beginTransaction();
}

function sqlsrv_commit($conn): bool
{
    return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->commit();
}

function sqlsrv_rollback($conn): bool
{
    return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->rollBack();
}

function sqlsrv_mysql_translate_query(string $sql): string
{
    $query = str_replace(["\r\n", "\r"], "\n", $sql);
    $query = preg_replace('/\bdbo\./i', '', $query);
    $query = preg_replace('/\[([A-Za-z0-9_]+)\]/', '`$1`', $query);

    $query = preg_replace('/FORMAT\(([^,]+),\s*\'yyyy\'\)\s*=\s*FORMAT\(GETDATE\(\),\s*\'yyyy\'\)/i', 'YEAR($1) = YEAR(NOW())', $query);
    $query = preg_replace('/CAST\(GETDATE\(\)\s+AS\s+DATE\)/i', 'CURDATE()', $query);
    $query = preg_replace('/GETDATE\(\)/i', 'NOW()', $query);
    $query = preg_replace('/SYSDATETIME\(\)/i', 'NOW()', $query);
    $query = preg_replace('/CONVERT\(VARCHAR,\s*([^)]+),\s*23\)/i', 'DATE($1)', $query);

    $query = preg_replace(
        '/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s+WHERE\s+Datum\s+<\s+CURDATE\(\)\)\)/i',
        'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch WHERE Datum < CURDATE()), INTERVAL $1 DAY)',
        $query
    );
    $query = preg_replace(
        '/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s*\)\)/i',
        'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch), INTERVAL $1 DAY)',
        $query
    );
    $query = preg_replace('/DATEADD\(DAY,\s*(-?\d+),\s*NOW\(\)\)/i', 'DATE_ADD(NOW(), INTERVAL $1 DAY)', $query);

    $limit = null;
    $query = preg_replace_callback('/^\s*SELECT\s+TOP\s+\(?\s*(\d+)\s*\)?\s+/i', function (array $matches) use (&$limit): string {
        $limit = (int)$matches[1];
        return 'SELECT ';
    }, $query);

    if ($limit !== null && !preg_match('/\bLIMIT\s+\d+\s*$/i', trim($query))) {
        $query = rtrim($query, " \t\n\r\0\x0B;") . ' LIMIT ' . $limit;
    }

    return $query;
}

/**
 * PDO is stricter than the SQL Server driver used by the legacy app. Some
 * legacy calls pass unused parameters, and one CSV lookup passes one value for
 * two equivalent placeholders. Normalize this only in the dev adapter.
 *
 * @param array<int, mixed> $params
 * @return array<int, mixed>
 */
function sqlsrv_mysql_normalize_params(string $sql, array $params): array
{
    $normalized = array_values($params);
    $placeholderCount = substr_count($sql, '?');

    if ($placeholderCount === 0) {
        return [];
    }

    if (count($normalized) > $placeholderCount) {
        return array_slice($normalized, 0, $placeholderCount);
    }

    if (count($normalized) < $placeholderCount && count($normalized) > 0) {
        $last = $normalized[count($normalized) - 1];
        while (count($normalized) < $placeholderCount) {
            $normalized[] = $last;
        }
    }

    return $normalized;
}

/**
 * @param array<string, mixed> $row
 * @return array<string, mixed>
 */
function sqlsrv_mysql_normalize_row(array $row): array
{
    foreach ($row as $key => $value) {
        if ($value === null || $value instanceof DateTimeInterface) {
            continue;
        }

        if (is_string($value) && preg_match('/(datum|date|gueltig_bis|valid_)/i', (string)$key)) {
            try {
                $row[$key] = new DateTime($value);
            } catch (Throwable $e) {
                $row[$key] = $value;
            }
        }
    }

    return $row;
}
