package com.tasteby.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Set;

@Service
public class CacheService {

    private static final Logger log = LoggerFactory.getLogger(CacheService.class);
    private static final String PREFIX = "tasteby:";

    private final StringRedisTemplate redis;
    private final ObjectMapper mapper;
    private final Duration ttl;
    private boolean disabled = false;

    public CacheService(StringRedisTemplate redis, ObjectMapper mapper,
                        @Value("${app.cache.ttl-seconds:600}") int ttlSeconds) {
        this.redis = redis;
        this.mapper = mapper;
        this.ttl = Duration.ofSeconds(ttlSeconds);
        // #276 — ping 연결 자원 누수 방지: try-with-resources
        var factory = redis.getConnectionFactory();
        if (factory == null) {
            log.warn("Redis ConnectionFactory is null, caching disabled");
            disabled = true;
            return;
        }
        try (var conn = factory.getConnection()) {
            conn.ping();
            log.info("Redis connected");
        } catch (Exception e) {
            log.warn("Redis unavailable ({}), caching disabled", e.getMessage());
            disabled = true;
        }
    }

    public String makeKey(String... parts) {
        // #276 — null/빈 파트로 "tasteby::" 같은 잘못된 키 생성 방지
        if (parts == null || parts.length == 0) {
            throw new IllegalArgumentException("makeKey requires at least one part");
        }
        for (String p : parts) {
            if (p == null) throw new IllegalArgumentException("makeKey parts must not be null");
        }
        return PREFIX + String.join(":", parts);
    }

    public <T> T get(String key, Class<T> type) {
        if (disabled) return null;
        try {
            String val = redis.opsForValue().get(key);
            if (val != null) {
                return mapper.readValue(val, type);
            }
        } catch (Exception e) {
            log.debug("Cache get error: {}", e.getMessage());
        }
        return null;
    }

    public String getRaw(String key) {
        if (disabled) return null;
        try {
            return redis.opsForValue().get(key);
        } catch (Exception e) {
            log.debug("Cache get error: {}", e.getMessage());
            return null;
        }
    }

    public void set(String key, Object value) {
        if (disabled) return;
        try {
            String json = mapper.writeValueAsString(value);
            redis.opsForValue().set(key, json, ttl);
        } catch (JsonProcessingException e) {
            log.debug("Cache set error: {}", e.getMessage());
        }
    }

    public void flush() {
        if (disabled) return;
        try {
            Set<String> keys = redis.keys(PREFIX + "*");
            if (keys != null && !keys.isEmpty()) {
                redis.delete(keys);
            }
            log.info("Cache flushed");
        } catch (Exception e) {
            log.debug("Cache flush error: {}", e.getMessage());
        }
    }

    // #290 — 단일 키 삭제 (캐시 역직렬화 실패 시 자동 evict 등에 사용)
    public void del(String key) {
        if (disabled) return;
        try {
            redis.delete(key);
        } catch (Exception e) {
            log.debug("Cache del error: {}", e.getMessage());
        }
    }
}
