<?php
namespace GemAcc;

use PDO;
use Throwable;

/**
 * DB-backed conversation state storage for Telegram bot flows.
 * Replaces legacy state-cache.json and prevents concurrent webhook workers
 * from overwriting each other's state writes.
 */
final class StateService
{
    private PDO $pdo;
    private int $defaultTtl;

    public function __construct(PDO $pdo, int $defaultTtl = 7200)
    {
        $this->pdo = $pdo;
        $this->defaultTtl = max(60, $defaultTtl);
        $this->ensureSchema();
    }

    public function set(int $userId, string $mode, $payload = null, ?int $ttlSeconds = null): void
    {
        if ($userId <= 0 || $mode === '') return;
        $ttl = max(60, (int)($ttlSeconds ?? $this->defaultTtl));
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($json === false) $json = 'null';
        $stmt = $this->pdo->prepare("INSERT INTO bot_user_states(user_id, mode, payload_json, created_at, updated_at, expires_at) VALUES(?, ?, ?, NOW(), NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND)) ON DUPLICATE KEY UPDATE mode=VALUES(mode), payload_json=VALUES(payload_json), updated_at=NOW(), expires_at=VALUES(expires_at)");
        $stmt->execute([$userId, $mode, $json, $ttl]);
    }

    public function get(int $userId): ?array
    {
        if ($userId <= 0) return null;
        $stmt = $this->pdo->prepare("SELECT mode, payload_json, expires_at FROM bot_user_states WHERE user_id=? LIMIT 1");
        $stmt->execute([$userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) return null;
        if (!empty($row['expires_at']) && strtotime((string)$row['expires_at']) < time()) {
            $this->delete($userId);
            return null;
        }
        $payload = null;
        if (array_key_exists('payload_json', $row) && $row['payload_json'] !== null && $row['payload_json'] !== '') {
            $decoded = json_decode((string)$row['payload_json'], true);
            $payload = (json_last_error() === JSON_ERROR_NONE) ? $decoded : null;
        }
        return ['mode' => (string)$row['mode'], 'payload' => $payload, 't' => isset($row['expires_at']) ? strtotime((string)$row['expires_at']) : time()];
    }

    public function delete(int $userId): void
    {
        if ($userId <= 0) return;
        $this->pdo->prepare("DELETE FROM bot_user_states WHERE user_id=?")->execute([$userId]);
    }

    public function cleanupExpired(): int
    {
        try {
            $stmt = $this->pdo->prepare("DELETE FROM bot_user_states WHERE expires_at IS NOT NULL AND expires_at < NOW()");
            $stmt->execute();
            return (int)$stmt->rowCount();
        } catch (Throwable $e) {
            return 0;
        }
    }

    public function ensureSchema(): void
    {
        try {
            $this->pdo->exec("CREATE TABLE IF NOT EXISTS bot_user_states (user_id BIGINT NOT NULL PRIMARY KEY, mode VARCHAR(100) NOT NULL, payload_json LONGTEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, expires_at DATETIME NULL, KEY idx_state_expires (expires_at), KEY idx_state_mode (mode)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
        } catch (Throwable $e) {
            error_log('[StateService] ensureSchema failed: ' . $e->getMessage());
        }
    }
}
