"""
Redis 캐시 관리 유틸리티
성능 향상을 위한 캐싱 전략 구현
"""
import json
import redis
from typing import Any, Optional, Dict, List
from datetime import timedelta
import hashlib
import pickle

from ..config import get_settings
from .logger import get_logger

settings = get_settings()
logger = get_logger(__name__)


class CacheManager:
    """Redis 캐시 관리 클래스"""
    
    def __init__(self):
        try:
            # Redis 연결 설정
            self.redis_client = redis.from_url(
                settings.redis.url,
                decode_responses=False,  # 바이너리 데이터 지원
                socket_connect_timeout=5,
                socket_timeout=5,
                retry_on_timeout=True
            )
            
            # 연결 테스트
            self.redis_client.ping()
            logger.info("Redis 연결 성공")
            
        except Exception as e:
            logger.error(f"Redis 연결 실패: {e}")
            self.redis_client = None
    
    def _generate_key(self, prefix: str, *args, **kwargs) -> str:
        """캐시 키 생성"""
        # 인자들을 문자열로 변환하여 해시 생성
        key_parts = [str(arg) for arg in args]
        key_parts.extend([f"{k}:{v}" for k, v in sorted(kwargs.items())])
        
        if key_parts:
            key_hash = hashlib.md5("|".join(key_parts).encode()).hexdigest()[:8]
            return f"tkmp:{prefix}:{key_hash}"
        else:
            return f"tkmp:{prefix}"
    
    def get(self, key: str) -> Optional[Any]:
        """캐시에서 데이터 조회"""
        if not self.redis_client:
            return None
        
        try:
            data = self.redis_client.get(key)
            if data:
                return pickle.loads(data)
            return None
        except Exception as e:
            logger.warning(f"캐시 조회 실패 - key: {key}, error: {e}")
            return None
    
    def set(self, key: str, value: Any, expire: int = 3600) -> bool:
        """캐시에 데이터 저장"""
        if not self.redis_client:
            return False
        
        try:
            serialized_data = pickle.dumps(value)
            result = self.redis_client.setex(key, expire, serialized_data)
            logger.debug(f"캐시 저장 - key: {key}, expire: {expire}s")
            return result
        except Exception as e:
            logger.warning(f"캐시 저장 실패 - key: {key}, error: {e}")
            return False
    
    def delete(self, key: str) -> bool:
        """캐시에서 데이터 삭제"""
        if not self.redis_client:
            return False
        
        try:
            result = self.redis_client.delete(key)
            logger.debug(f"캐시 삭제 - key: {key}")
            return bool(result)
        except Exception as e:
            logger.warning(f"캐시 삭제 실패 - key: {key}, error: {e}")
            return False
    
    def delete_pattern(self, pattern: str) -> int:
        """패턴에 맞는 캐시 키들 삭제"""
        if not self.redis_client:
            return 0
        
        try:
            keys = self.redis_client.keys(pattern)
            if keys:
                deleted = self.redis_client.delete(*keys)
                logger.info(f"패턴 캐시 삭제 - pattern: {pattern}, deleted: {deleted}")
                return deleted
            return 0
        except Exception as e:
            logger.warning(f"패턴 캐시 삭제 실패 - pattern: {pattern}, error: {e}")
            return 0
    
    def exists(self, key: str) -> bool:
        """캐시 키 존재 여부 확인"""
        if not self.redis_client:
            return False
        
        try:
            return bool(self.redis_client.exists(key))
        except Exception as e:
            logger.warning(f"캐시 존재 확인 실패 - key: {key}, error: {e}")
            return False
    
    def get_ttl(self, key: str) -> int:
        """캐시 TTL 조회"""
        if not self.redis_client:
            return -1
        
        try:
            return self.redis_client.ttl(key)
        except Exception as e:
            logger.warning(f"캐시 TTL 조회 실패 - key: {key}, error: {e}")
            return -1


class TKMPCache:
    """TK-MP 프로젝트 전용 캐시 래퍼"""
    
    def __init__(self):
        self.cache = CacheManager()
        
        # 캐시 TTL 설정 (초 단위)
        self.ttl_config = {
            "file_list": 300,        # 5분 - 파일 목록
            "material_list": 600,    # 10분 - 자재 목록
            "job_list": 1800,        # 30분 - 작업 목록
            "classification": 3600,  # 1시간 - 분류 결과
            "statistics": 900,       # 15분 - 통계 데이터
            "comparison": 1800,      # 30분 - 리비전 비교
        }
    
    def get_file_list(self, job_no: Optional[str] = None, show_history: bool = False) -> Optional[List[Dict]]:
        """파일 목록 캐시 조회"""
        key = self.cache._generate_key("files", job_no=job_no, history=show_history)
        return self.cache.get(key)
    
    def set_file_list(self, files: List[Dict], job_no: Optional[str] = None, show_history: bool = False) -> bool:
        """파일 목록 캐시 저장"""
        key = self.cache._generate_key("files", job_no=job_no, history=show_history)
        return self.cache.set(key, files, self.ttl_config["file_list"])
    
    def get_material_list(self, file_id: int) -> Optional[List[Dict]]:
        """자재 목록 캐시 조회"""
        key = self.cache._generate_key("materials", file_id=file_id)
        return self.cache.get(key)
    
    def set_material_list(self, materials: List[Dict], file_id: int) -> bool:
        """자재 목록 캐시 저장"""
        key = self.cache._generate_key("materials", file_id=file_id)
        return self.cache.set(key, materials, self.ttl_config["material_list"])
    
    def get_job_list(self) -> Optional[List[Dict]]:
        """작업 목록 캐시 조회"""
        key = self.cache._generate_key("jobs")
        return self.cache.get(key)
    
    def set_job_list(self, jobs: List[Dict]) -> bool:
        """작업 목록 캐시 저장"""
        key = self.cache._generate_key("jobs")
        return self.cache.set(key, jobs, self.ttl_config["job_list"])
    
    def get_classification_result(self, description: str, category: str) -> Optional[Dict]:
        """분류 결과 캐시 조회"""
        key = self.cache._generate_key("classification", desc=description, cat=category)
        return self.cache.get(key)
    
    def set_classification_result(self, result: Dict, description: str, category: str) -> bool:
        """분류 결과 캐시 저장"""
        key = self.cache._generate_key("classification", desc=description, cat=category)
        return self.cache.set(key, result, self.ttl_config["classification"])
    
    def get_statistics(self, job_no: str, stat_type: str) -> Optional[Dict]:
        """통계 데이터 캐시 조회"""
        key = self.cache._generate_key("stats", job_no=job_no, type=stat_type)
        return self.cache.get(key)
    
    def set_statistics(self, stats: Dict, job_no: str, stat_type: str) -> bool:
        """통계 데이터 캐시 저장"""
        key = self.cache._generate_key("stats", job_no=job_no, type=stat_type)
        return self.cache.set(key, stats, self.ttl_config["statistics"])
    
    def get_revision_comparison(self, job_no: str, rev1: str, rev2: str) -> Optional[Dict]:
        """리비전 비교 결과 캐시 조회"""
        key = self.cache._generate_key("comparison", job_no=job_no, rev1=rev1, rev2=rev2)
        return self.cache.get(key)
    
    def set_revision_comparison(self, comparison: Dict, job_no: str, rev1: str, rev2: str) -> bool:
        """리비전 비교 결과 캐시 저장"""
        key = self.cache._generate_key("comparison", job_no=job_no, rev1=rev1, rev2=rev2)
        return self.cache.set(key, comparison, self.ttl_config["comparison"])
    
    def invalidate_job_cache(self, job_no: str):
        """특정 작업의 모든 캐시 무효화"""
        patterns = [
            f"tkmp:files:*job_no:{job_no}*",
            f"tkmp:materials:*job_no:{job_no}*",
            f"tkmp:stats:*job_no:{job_no}*",
            f"tkmp:comparison:*job_no:{job_no}*"
        ]
        
        total_deleted = 0
        for pattern in patterns:
            deleted = self.cache.delete_pattern(pattern)
            total_deleted += deleted
        
        logger.info(f"작업 캐시 무효화 완료 - job_no: {job_no}, deleted: {total_deleted}")
        return total_deleted
    
    def invalidate_file_cache(self, file_id: int):
        """특정 파일의 모든 캐시 무효화"""
        patterns = [
            f"tkmp:materials:*file_id:{file_id}*",
            f"tkmp:files:*"  # 파일 목록도 갱신 필요
        ]
        
        total_deleted = 0
        for pattern in patterns:
            deleted = self.cache.delete_pattern(pattern)
            total_deleted += deleted
        
        logger.info(f"파일 캐시 무효화 완료 - file_id: {file_id}, deleted: {total_deleted}")
        return total_deleted
    
    def get_cache_info(self) -> Dict[str, Any]:
        """캐시 상태 정보 조회"""
        if not self.cache.redis_client:
            return {"status": "disconnected"}
        
        try:
            info = self.cache.redis_client.info()
            return {
                "status": "connected",
                "used_memory": info.get("used_memory_human", "N/A"),
                "connected_clients": info.get("connected_clients", 0),
                "total_commands_processed": info.get("total_commands_processed", 0),
                "keyspace_hits": info.get("keyspace_hits", 0),
                "keyspace_misses": info.get("keyspace_misses", 0),
                "hit_rate": round(
                    info.get("keyspace_hits", 0) / 
                    max(info.get("keyspace_hits", 0) + info.get("keyspace_misses", 0), 1) * 100, 2
                )
            }
        except Exception as e:
            logger.error(f"캐시 정보 조회 실패: {e}")
            return {"status": "error", "error": str(e)}


# 전역 캐시 인스턴스
tkmp_cache = TKMPCache()
