<?php

declare(strict_types=1);

namespace App\Framework\Database\QueryOptimization\ValueObjects;

use InvalidArgumentException;

/**
 * Query Log Entry - Represents a single executed database query
 */
final readonly class QueryLog
{
    public function __construct(
        public string $sql,
        public array $bindings,
        public float $executionTimeMs,
        public string $stackTrace,
        public int $rowCount = 0,
        public ?string $callerClass = null,
        public ?string $callerMethod = null,
        public int $callerLine = 0
    ) {
        if ($executionTimeMs < 0) {
            throw new InvalidArgumentException('Execution time cannot be negative');
        }

        if ($rowCount < 0) {
            throw new InvalidArgumentException('Row count cannot be negative');
        }
    }

    /**
     * Extract SQL pattern by removing specific values
     */
    public function getPattern(): string
    {
        // Normalize SQL: remove specific values, keep structure
        $pattern = $this->sql;

        // Replace string literals
        $pattern = preg_replace("/'[^']*'/", '?', $pattern);

        // Replace numbers
        $pattern = preg_replace('/\b\d+\b/', '?', $pattern);

        // Replace IN clauses with multiple values
        $pattern = preg_replace('/IN\s*\([^)]+\)/i', 'IN (?)', $pattern);

        // Normalize whitespace
        $pattern = preg_replace('/\s+/', ' ', $pattern);

        return trim($pattern);
    }

    /**
     * Check if this query is a SELECT
     */
    public function isSelect(): bool
    {
        return stripos(trim($this->sql), 'SELECT') === 0;
    }

    /**
     * Check if this query is an INSERT
     */
    public function isInsert(): bool
    {
        return stripos(trim($this->sql), 'INSERT') === 0;
    }

    /**
     * Check if this query is an UPDATE
     */
    public function isUpdate(): bool
    {
        return stripos(trim($this->sql), 'UPDATE') === 0;
    }

    /**
     * Check if this query is a DELETE
     */
    public function isDelete(): bool
    {
        return stripos(trim($this->sql), 'DELETE') === 0;
    }

    /**
     * Extract table name from query
     */
    public function getTableName(): ?string
    {
        // Match FROM clause
        if (preg_match('/FROM\s+`?(\w+)`?/i', $this->sql, $matches)) {
            return $matches[1];
        }

        // Match INSERT INTO
        if (preg_match('/INSERT\s+INTO\s+`?(\w+)`?/i', $this->sql, $matches)) {
            return $matches[1];
        }

        // Match UPDATE
        if (preg_match('/UPDATE\s+`?(\w+)`?/i', $this->sql, $matches)) {
            return $matches[1];
        }

        // Match DELETE FROM
        if (preg_match('/DELETE\s+FROM\s+`?(\w+)`?/i', $this->sql, $matches)) {
            return $matches[1];
        }

        return null;
    }

    /**
     * Check if query has WHERE clause
     */
    public function hasWhereClause(): bool
    {
        return stripos($this->sql, 'WHERE') !== false;
    }

    /**
     * Check if query has JOIN
     */
    public function hasJoin(): bool
    {
        return preg_match('/\b(INNER|LEFT|RIGHT|FULL|CROSS)\s+JOIN\b/i', $this->sql) === 1;
    }

    /**
     * Extract caller info from stack trace
     */
    public function getCallerInfo(): string
    {
        if ($this->callerClass && $this->callerMethod) {
            return "{$this->callerClass}::{$this->callerMethod}:{$this->callerLine}";
        }

        return 'Unknown';
    }

    /**
     * Check if this query is slow (>100ms)
     */
    public function isSlow(float $thresholdMs = 100.0): bool
    {
        return $this->executionTimeMs > $thresholdMs;
    }

    public function toArray(): array
    {
        return [
            'sql' => $this->sql,
            'pattern' => $this->getPattern(),
            'bindings' => $this->bindings,
            'execution_time_ms' => $this->executionTimeMs,
            'row_count' => $this->rowCount,
            'table_name' => $this->getTableName(),
            'caller' => $this->getCallerInfo(),
            'is_slow' => $this->isSlow(),
        ];
    }
}
