<?php

/**
 * Драйвер кешу в базі даних
 *
 * @package Flowaxy\Infrastructure\Cache\Drivers
 * @version 1.0.0
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Cache\Drivers;

use Flowaxy\Infrastructure\Cache\CacheDriverInterface;
use Flowaxy\Infrastructure\Persistence\Database\Database;
use Flowaxy\Support\Facades\Log;
use Throwable;

final class DatabaseCacheDriver implements CacheDriverInterface
{
    private ?object $db = null;
    private string $tableName = 'cache';

    public function __construct(?object $db = null, string $tableName = 'cache')
    {
        $this->db = $db;
        $this->tableName = $tableName;
        $this->ensureTable();

        try {
            Log::Debug('DatabaseCacheDriver::__construct: Database cache driver initialized', ['table' => $this->tableName]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }
    }

    /**
     * {@inheritDoc}
     */
    public function get(string $key, mixed $default = null): mixed
    {
        try {
            Log::Debug('DatabaseCacheDriver::get: Retrieving from database cache', ['key' => $key, 'table' => $this->tableName]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }

        try {
            $db = $this->getDatabase();
            if (!$db) {
                try {
                    Log::Warning('DatabaseCacheDriver::get: Database connection not available', ['key' => $key]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return $default;
            }

            $stmt = $db->query(
                "SELECT value, expires FROM {$this->tableName} WHERE cache_key = ?",
                [$key]
            );

            $row = $stmt->fetch(\PDO::FETCH_ASSOC);

            if (!$row) {
                try {
                    Log::Debug('DatabaseCacheDriver::get: Key not found in database cache', ['key' => $key]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return $default;
            }

            // Перевіряємо термін дії
            if ($row['expires'] > 0 && $row['expires'] < time()) {
                $this->delete($key);
                try {
                    Log::Debug('DatabaseCacheDriver::get: Cache expired, deleted', ['key' => $key]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return $default;
            }

            try {
                Log::Debug('DatabaseCacheDriver::get: Retrieved from database cache', ['key' => $key]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return unserialize($row['value'], ['allowed_classes' => false]);
        } catch (\Exception $e) {
            try {
                Log::Error('DatabaseCacheDriver::get: Database error', [
                    'key' => $key,
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return $default;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        try {
            Log::Debug('DatabaseCacheDriver::set: Setting value in database cache', ['key' => $key, 'ttl' => $ttl, 'table' => $this->tableName]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }

        try {
            $db = $this->getDatabase();
            if (!$db) {
                try {
                    Log::Warning('DatabaseCacheDriver::set: Database connection not available', ['key' => $key]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return false;
            }

            $expires = $ttl !== null ? time() + $ttl : 0;
            $serialized = serialize($value);

            $db->query(
                "INSERT INTO {$this->tableName} (cache_key, value, expires, created_at)
                 VALUES (?, ?, ?, ?)
                 ON DUPLICATE KEY UPDATE value = ?, expires = ?, created_at = ?",
                [$key, $serialized, $expires, time(), $serialized, $expires, time()]
            );

            try {
                Log::Info('DatabaseCacheDriver::set: Value set in database cache', ['key' => $key]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return true;
        } catch (\Exception $e) {
            try {
                Log::Error('DatabaseCacheDriver::set: Database error', [
                    'key' => $key,
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function delete(string $key): bool
    {
        try {
            Log::Debug('DatabaseCacheDriver::delete: Deleting from database cache', ['key' => $key, 'table' => $this->tableName]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }

        try {
            $db = $this->getDatabase();
            if (!$db) {
                try {
                    Log::Warning('DatabaseCacheDriver::delete: Database connection not available', ['key' => $key]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return false;
            }

            // ВАЖЛИВО: використовуємо query з logQuery=false, щоб уникнути рекурсії через Database::execute(),
            // який всередині викликає invalidateTableCache() і знову потрапляє в Cache/DatabaseCacheDriver.
            $db->query("DELETE FROM {$this->tableName} WHERE cache_key = ?", [$key], false);

            try {
                Log::Info('DatabaseCacheDriver::delete: Deleted from database cache', ['key' => $key]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return true;
        } catch (\Exception $e) {
            try {
                Log::Error('DatabaseCacheDriver::delete: Database error', [
                    'key' => $key,
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function has(string $key): bool
    {
        try {
            $db = $this->getDatabase();
            if (!$db) {
                return false;
            }

            $stmt = $db->query(
                "SELECT expires FROM {$this->tableName} WHERE cache_key = ?",
                [$key]
            );

            $row = $stmt->fetch(\PDO::FETCH_ASSOC);

            if (!$row) {
                return false;
            }

            // Перевіряємо термін дії
            if ($row['expires'] > 0 && $row['expires'] < time()) {
                $this->delete($key);
                return false;
            }

            return true;
        } catch (\Exception $e) {
            try {
                Log::Error('DatabaseCacheDriver::has: Database error', [
                    'key' => $key,
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function clear(): bool
    {
        try {
            Log::Debug('DatabaseCacheDriver::clear: Clearing database cache', ['table' => $this->tableName]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }

        try {
            $db = $this->getDatabase();
            if (!$db) {
                try {
                    Log::Warning('DatabaseCacheDriver::clear: Database connection not available');
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return false;
            }

            // Так само як у delete(): використовуємо query з logQuery=false, щоб не запускати invalidateTableCache().
            $db->query("TRUNCATE TABLE {$this->tableName}", [], false);

            try {
                Log::Info('DatabaseCacheDriver::clear: Database cache cleared', ['table' => $this->tableName]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return true;
        } catch (\Exception $e) {
            try {
                Log::Error('DatabaseCacheDriver::clear: Database error', [
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    /**
     * Отримання підключення до БД
     */
    private function getDatabase(): ?object
    {
        if ($this->db !== null) {
            return $this->db;
        }

        // Спробуємо отримати Database через глобальну функцію або клас
        if (class_exists(Database::class)) {
            return Database::getInstance();
        }

        return null;
    }

    /**
     * Створення таблиці кешу, якщо не існує
     */
    private function ensureTable(): void
    {
        try {
            $db = $this->getDatabase();
            if (!$db) {
                try {
                    Log::Warning('DatabaseCacheDriver::ensureTable: Database connection not available');
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return;
            }

            try {
                Log::Debug('DatabaseCacheDriver::ensureTable: Ensuring cache table exists', ['table' => $this->tableName]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            // ВАЖЛИВО: ensureTable може виконуватись на ранній фазі boot.
            // Тут НЕ можна вмикати логування запитів/хуки (може спричиняти рекурсію через Facades/Hooks).
            // Тому виконуємо запит з logQuery=false.
            $db->query("
                CREATE TABLE IF NOT EXISTS {$this->tableName} (
                    cache_key VARCHAR(255) PRIMARY KEY,
                    value LONGTEXT NOT NULL,
                    expires INT UNSIGNED NOT NULL DEFAULT 0,
                    created_at INT UNSIGNED NOT NULL,
                    INDEX idx_expires (expires)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            ", [], false);

            try {
                Log::Info('DatabaseCacheDriver::ensureTable: Cache table ensured', ['table' => $this->tableName]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }
        } catch (\Exception $e) {
            try {
                Log::Warning('DatabaseCacheDriver::ensureTable: Table creation error (may already exist)', [
                    'table' => $this->tableName,
                    'error' => $e->getMessage(),
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            // Таблиця вже існує або помилка створення
        }
    }
}
