/*
 * Copyright (c) 2025 SimcuTeam. All rights reserved.
 * 遵循 MIT 许可证。
 */

package simcu::simapi.helpers

import std.collection.*
import std.collection.concurrent.*
import std.convert.*
import std.time.*
import redis.client.*
import simcu::serialization.*
import simcu::simapi.configurations.*
import simcu::simapi.exceptions.*

/// InMemory 存储项：缓存值 + 过期时间（epoch 毫秒，0 表示不过期）
private struct CacheEntry {
    var value: String
    var expireAt: Int64

    public init(value: String, expireAt: Int64) {
        this.value = value
        this.expireAt = expireAt
    }
}

/**
 * 缓存助手：Key 自动加前缀 "SimApi:Cache:"。
 * 存储后端与 SimApiAuth 一致：配置了 Redis 用 Redis（可带密码/DB 索引），否则 InMemory。
 * InMemory 模式同样支持过期。
 */
public class SimApiCache {
    private static let prefix = "SimApi:Cache:"

    private var _redis: ?RedisClient = None
    private let _store = ConcurrentHashMap<String, CacheEntry>()

    /**
     * 创建缓存（依赖注入 SimApiOptions）。
     * @param options SimApi 配置（redisConfiguration 非空时使用 Redis，否则 InMemory）。
     */
    public init(options: SimApiOptions) {
        let redisConfiguration = options.redisConfiguration
        if (!redisConfiguration.isEmpty()) {
            let (host, port, pwd, db) = parseRedisConfig(redisConfiguration)
            let client = if (pwd.isEmpty()) {
                // autoHello=false：跳过 HELLO 3 协商（Redis 8.x 的 RESP3 响应含 modules 等嵌套结构，
                // redis-client 库解析偶发失败），直接用 RESP2 协议
                RedisClient(host, port, autoHello: false)
            } else {
                RedisClient(host, port, autoHello: false, authPassword: Some(pwd))
            }
            if (db > 0) {
                try {
                    client.executeString(["SELECT", db.toString()])
                } catch (_: Exception) {
                }
            }
            _redis = Some(client)
        }
    }

    /**
     * 设置缓存。
     * @param key 缓存键。
     * @param value 缓存值（不能为 null）。
     * @param expireSeconds 过期秒数（可选，<=0 表示不过期）。
     */
    public func set(key: String, value: Any, expireSeconds!: Int64 = -1): Unit {
        let json = SimApiUtil.json(Some(value))
        if (let Some(redis) <- _redis) {
            if (expireSeconds > 0) {
                redis.set("${prefix}${key}", Blob.fromUtf8(json), ex: Some(expireSeconds))
            } else {
                redis.set("${prefix}${key}", Blob.fromUtf8(json))
            }
        } else {
            let expireAt = if (expireSeconds > 0) { nowMillis() + expireSeconds * 1000 } else { 0 }
            _store["${prefix}${key}"] = CacheEntry(json, expireAt)
        }
    }

    /**
     * 移除缓存。
     */
    public func remove(key: String): Unit {
        if (let Some(redis) <- _redis) {
            redis.del(["${prefix}${key}"])
        } else {
            _store.remove("${prefix}${key}")
        }
    }

    /**
     * 缓存 Key 是否存在。
     */
    public func hasKey(key: String): Bool {
        getString(key) != None
    }

    /**
     * 获取特定类型缓存（从 JSON 反序列化，使用 simapi_serialization）。
     */
    public func get<T>(key: String): ?T {
        match (getString(key)) {
            case Some(json) => Some(JsonSerializer.deserialize<T>(json))
            case None => None
        }
    }

    /**
     * 获取 string 类型缓存（仓颉不支持按泛型重载，故拆分为 getString/get<T>）。
     */
    public func getString(key: String): ?String {
        if (let Some(redis) <- _redis) {
            return match (redis.get("${prefix}${key}")) {
                case Some(blob) => Some(blob.toUtf8())
                case None => None
            }
        }
        return match (_store.get("${prefix}${key}")) {
            case Some(entry) =>
                if (entry.expireAt > 0 && entry.expireAt < nowMillis()) {
                    _store.remove("${prefix}${key}")
                    None
                } else {
                    Some(entry.value)
                }
            case None => None
        }
    }

    /// 解析 Redis 连接串，支持：host:port | host:port,password=xxx | host:port,password=xxx,db=2
    private static func parseRedisConfig(config: String): (String, UInt16, String, Int64) {
        var host = "127.0.0.1"
        var port = 6379u16
        var pwd = ""
        var db: Int64 = 0
        let segments = config.split(",")
        let hp = segments[0].split(":")
        if (hp.size == 2) {
            host = hp[0]
            port = UInt16.parse(hp[1])
        } else {
            host = config
        }
        for (i in 1..segments.size) {
            let seg = segments[i].trimAscii()
            match (seg.indexOf("=")) {
                case Some(idx) =>
                    let key = seg[0..idx].trimAscii().toAsciiLower()
                    let value = seg[idx + 1..].trimAscii()
                    match (key) {
                        case "password" | "pwd" => pwd = value
                        case "db" | "database" | "defaultdatabase" => db = Int64.parse(value)
                        case _ => ()
                    }
                case None => ()
            }
        }
        (host, port, pwd, db)
    }

    /// 当前时间（epoch 毫秒）
    private static func nowMillis(): Int64 {
        DateTime.nowUTC().toUnixTimeStamp().toMilliseconds()
    }
}
