<?php

declare(strict_types=1);

namespace App\Framework\Telemetry\Middleware;

use App\Framework\Database\Middleware\QueryContext;
use App\Framework\Database\Middleware\QueryMiddleware;
use App\Framework\Telemetry\UnifiedTelemetryService;

/**
 * Database middleware for telemetry query tracing
 */
final readonly class TelemetryDatabaseMiddleware implements QueryMiddleware
{
    /**
     * @param UnifiedTelemetryService $telemetryService Telemetry service
     */
    public function __construct(
        private UnifiedTelemetryService $telemetryService
    ) {
    }

    /**
     * Process a query operation through the middleware
     */
    public function process(QueryContext $context, callable $next): mixed
    {
        // Skip if telemetry is disabled
        if (! $this->telemetryService->isEnabled()) {
            return $next();
        }

        $query = $context->sql;
        $bindings = $context->parameters;
        $queryType = $this->getQueryType($query);

        // Create attributes for the operation
        $attributes = [
            'db.system' => $this->getDatabaseSystem($context),
            'db.type' => 'sql',
            'db.operation' => $queryType,
            'db.statement' => $this->sanitizeQuery($query),
            'db.sql.table' => $this->extractTableName($query, $queryType),
            'db.user' => $context->connection->getConfig()->getUsername() ?? 'unknown',
            'net.peer.name' => $context->connection->getConfig()->getHost() ?? 'unknown',
            'net.peer.port' => $context->connection->getConfig()->getPort() ?? 0,
            'db.name' => $context->connection->getConfig()->getDatabase() ?? 'unknown',
            'db.parameters_count' => count($bindings),
        ];

        // Create an operation name for the query
        $operationName = "db.{$queryType}";

        // Trace the query execution
        return $this->telemetryService->trace(
            $operationName,
            'database',
            function () use ($next) {
                return $next();
            },
            $attributes
        );
    }

    /**
     * Get the priority of the middleware
     */
    public function getPriority(): int
    {
        return 90; // Run after performance middleware but before most other middleware
    }

    /**
     * Get the query type from the SQL query
     */
    private function getQueryType(string $query): string
    {
        $query = trim(strtoupper($query));

        if (str_starts_with($query, 'SELECT')) {
            return 'select';
        }
        if (str_starts_with($query, 'INSERT')) {
            return 'insert';
        }
        if (str_starts_with($query, 'UPDATE')) {
            return 'update';
        }
        if (str_starts_with($query, 'DELETE')) {
            return 'delete';
        }
        if (str_starts_with($query, 'CREATE')) {
            return 'create';
        }
        if (str_starts_with($query, 'DROP')) {
            return 'drop';
        }
        if (str_starts_with($query, 'ALTER')) {
            return 'alter';
        }
        if (str_starts_with($query, 'SHOW')) {
            return 'show';
        }
        if (str_starts_with($query, 'DESCRIBE') || str_starts_with($query, 'DESC')) {
            return 'describe';
        }
        if (str_starts_with($query, 'BEGIN') || str_starts_with($query, 'START')) {
            return 'transaction_begin';
        }
        if (str_starts_with($query, 'COMMIT')) {
            return 'transaction_commit';
        }
        if (str_starts_with($query, 'ROLLBACK')) {
            return 'transaction_rollback';
        }

        return 'other';
    }

    /**
     * Sanitize a query to remove sensitive data
     */
    private function sanitizeQuery(string $query): string
    {
        // Remove potential sensitive data from queries for logging
        $query = preg_replace('/\b\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}\b/', '****-****-****-****', $query);
        $query = preg_replace('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '***@***.***', $query);

        // Replace password values
        $query = preg_replace('/password\s*=\s*[\'"][^\'"]*[\'"]/i', 'password=\'***\'', $query);
        $query = preg_replace('/password\s*=\s*[^\s,;)]+/i', 'password=***', $query);

        // Truncate very long queries
        if (strlen($query) > 1000) {
            $query = substr($query, 0, 1000) . '...';
        }

        return $query;
    }

    /**
     * Extract the table name from a query
     */
    private function extractTableName(string $query, string $queryType): string
    {
        $query = trim($query);

        // Simple regex-based extraction - in a real implementation, this would be more robust
        $patterns = [
            'select' => '/FROM\s+`?([a-zA-Z0-9_]+)`?/i',
            'insert' => '/INSERT\s+INTO\s+`?([a-zA-Z0-9_]+)`?/i',
            'update' => '/UPDATE\s+`?([a-zA-Z0-9_]+)`?/i',
            'delete' => '/DELETE\s+FROM\s+`?([a-zA-Z0-9_]+)`?/i',
            'create' => '/CREATE\s+TABLE\s+`?([a-zA-Z0-9_]+)`?/i',
            'drop' => '/DROP\s+TABLE\s+`?([a-zA-Z0-9_]+)`?/i',
            'alter' => '/ALTER\s+TABLE\s+`?([a-zA-Z0-9_]+)`?/i',
        ];

        if (isset($patterns[$queryType])) {
            if (preg_match($patterns[$queryType], $query, $matches)) {
                return $matches[1];
            }
        }

        return 'unknown';
    }

    /**
     * Get the database system from the connection
     */
    private function getDatabaseSystem(QueryContext $context): string
    {
        $driver = $context->connection->getConfig()->getDriver() ?? '';

        return match ($driver) {
            'mysql', 'mysqli' => 'mysql',
            'pgsql' => 'postgresql',
            'sqlite' => 'sqlite',
            'sqlsrv' => 'mssql',
            'oci' => 'oracle',
            default => $driver ?: 'unknown',
        };
    }
}
