<?php

declare(strict_types=1);

namespace App\Framework\Database\ValueObjects;

use InvalidArgumentException;

/**
 * Value Object for SQL queries
 * Encapsulates SQL statement with parameters for type safety and SQL injection prevention
 */
final readonly class SqlQuery
{
    public function __construct(
        public string $sql,
        public QueryParameters $parameters = new QueryParameters([])
    ) {
        if (empty(trim($sql))) {
            throw new InvalidArgumentException('SQL query cannot be empty');
        }

        if (strlen($sql) > 1000000) { // 1MB limit
            throw new InvalidArgumentException('SQL query is too large');
        }
    }

    public static function create(string $sql, array $parameters = []): self
    {
        return new self($sql, QueryParameters::fromArray($parameters));
    }

    public static function select(string $table, array $columns = ['*'], ?string $where = null): self
    {
        $columnList = implode(', ', $columns);
        $sql = "SELECT {$columnList} FROM {$table}";

        if ($where) {
            $sql .= " WHERE {$where}";
        }

        return new self($sql);
    }

    public static function insert(string $table, array $data): self
    {
        if (empty($data)) {
            throw new InvalidArgumentException('Insert data cannot be empty');
        }

        $columns = implode(', ', array_keys($data));
        $placeholders = ':' . implode(', :', array_keys($data));
        $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";

        return new self($sql, QueryParameters::fromArray($data));
    }

    public static function update(string $table, array $data, string $where): self
    {
        if (empty($data)) {
            throw new InvalidArgumentException('Update data cannot be empty');
        }

        $sets = [];
        foreach (array_keys($data) as $column) {
            $sets[] = "{$column} = :{$column}";
        }
        $setClause = implode(', ', $sets);

        $sql = "UPDATE {$table} SET {$setClause} WHERE {$where}";

        return new self($sql, QueryParameters::fromArray($data));
    }

    public static function delete(string $table, string $where): self
    {
        $sql = "DELETE FROM {$table} WHERE {$where}";

        return new self($sql);
    }

    public function withParameter(string $name, mixed $value): self
    {
        return new self($this->sql, $this->parameters->with($name, $value));
    }

    public function withParameters(array $parameters): self
    {
        return new self($this->sql, $this->parameters->merge($parameters));
    }

    public function isSelect(): bool
    {
        return preg_match('/^\s*SELECT\s+/i', $this->sql) === 1;
    }

    public function isInsert(): bool
    {
        return preg_match('/^\s*INSERT\s+/i', $this->sql) === 1;
    }

    public function isUpdate(): bool
    {
        return preg_match('/^\s*UPDATE\s+/i', $this->sql) === 1;
    }

    public function isDelete(): bool
    {
        return preg_match('/^\s*DELETE\s+/i', $this->sql) === 1;
    }

    public function isDDL(): bool
    {
        return preg_match('/^\s*(CREATE|ALTER|DROP)\s+/i', $this->sql) === 1;
    }

    public function isModifying(): bool
    {
        return $this->isInsert() || $this->isUpdate() || $this->isDelete() || $this->isDDL();
    }

    /**
     * Get SQL with parameters for logging/debugging
     * WARNING: Only use for debugging, never for execution
     */
    public function toDebugString(): string
    {
        $sql = $this->sql;
        foreach ($this->parameters->toArray() as $key => $value) {
            $replacement = $value === null ? 'NULL' : var_export($value, true);
            $sql = str_replace(":{$key}", $replacement, $sql);
        }

        return $sql;
    }

    public function toString(): string
    {
        return $this->sql;
    }

    public function __toString(): string
    {
        return $this->sql;
    }
}
