"""
C4 SDK Cache module for caching C4 API responses in Redis.

This module provides caching for C4 API responses using a shared Redis cache
(typically AWS ElastiCache). All services using the SDK share the same cache,
reducing load on C4.

The cache key format is compatible with the Java SDK's C4CachingService:
	{keyPrefix}:{cacheName}:{cacheKey}
	e.g., c4-sdk:c4-sdk-integration-settings:integration:1234:TAI

Environment variables:
	C4_SDK_CACHE_ENABLED: Enable/disable caching (default: false)
	C4_SDK_CACHE_REDIS_HOST: Redis host (e.g., ElastiCache endpoint)
	C4_SDK_CACHE_REDIS_PORT: Redis port (default: 6379)
	C4_SDK_CACHE_REDIS_PASSWORD: Redis password (optional)
	C4_SDK_CACHE_USE_SSL: Use SSL for Redis connection (default: false)
	C4_SDK_CACHE_TTL_HOURS: TTL in hours (default: 24)
	C4_SDK_CACHE_KEY_PREFIX: Cache key prefix (default: c4-sdk)
"""

import json
import os

from cc_py_commons.utils import json_logger

# Optional redis import - allows graceful fallback if not installed
try:
	import redis
except ImportError:
	redis = None

# Cache configuration from environment variables
CACHE_ENABLED = os.environ.get('C4_SDK_CACHE_ENABLED', 'false').lower() == 'true'
CACHE_REDIS_HOST = os.environ.get('C4_SDK_CACHE_REDIS_HOST')
CACHE_REDIS_PASSWORD = os.environ.get('C4_SDK_CACHE_REDIS_PASSWORD')
CACHE_USE_SSL = os.environ.get('C4_SDK_CACHE_USE_SSL', 'false').lower() == 'true'
CACHE_KEY_PREFIX = os.environ.get('C4_SDK_CACHE_KEY_PREFIX', 'c4-sdk')


def _safe_int(value, default):
	"""Safely parse an integer from a string value."""

	if value is None:
		return default

	try:
		return int(value)

	except (ValueError, TypeError):
		return default


CACHE_REDIS_PORT = _safe_int(os.environ.get('C4_SDK_CACHE_REDIS_PORT'), 6379)
CACHE_TTL_HOURS = _safe_int(os.environ.get('C4_SDK_CACHE_TTL_HOURS'), 24)

# Cache names (must match Java SDK's C4CacheConfiguration)
CACHE_INTEGRATION_SETTINGS = 'c4-sdk-integration-settings'

# Redis connection (lazy initialization)
_redis_client = None
_cache_available = True


def _get_redis_client():
	"""
	Get or create the Redis client connection.
	Returns None if caching is disabled or Redis is unavailable.
	"""
	global _redis_client, _cache_available

	if not CACHE_ENABLED:
		return None

	if not _cache_available:
		return None

	if _redis_client is not None:
		return _redis_client

	if not CACHE_REDIS_HOST:
		json_logger.warning(None, "C4 SDK caching is enabled but C4_SDK_CACHE_REDIS_HOST is not configured")
		_cache_available = False
		return None

	if redis is None:
		json_logger.warning(None, "C4 SDK caching requires redis package - falling back to direct API calls")
		_cache_available = False
		return None

	try:
		_redis_client = redis.Redis(
			host=CACHE_REDIS_HOST,
			port=CACHE_REDIS_PORT,
			password=CACHE_REDIS_PASSWORD if CACHE_REDIS_PASSWORD else None,
			ssl=CACHE_USE_SSL,
			decode_responses=True,
			socket_connect_timeout=5,
			socket_timeout=5
		)

		# Test connection
		_redis_client.ping()
		json_logger.info(None, "C4 SDK cache connected to Redis", host=CACHE_REDIS_HOST, port=CACHE_REDIS_PORT)
		return _redis_client

	except Exception as e:
		json_logger.warning(None, "C4 SDK cache Redis connection failed - falling back to direct API calls", error=str(e))
		_cache_available = False
		return None


def _build_cache_key(cache_name, key):
	"""
	Build a full Redis key in the format compatible with Java SDK.
	Format: {keyPrefix}:{cacheName}:{key}
	"""
	return f"{CACHE_KEY_PREFIX}:{cache_name}:{key}"


def get_from_cache(cache_name, key, account_id=None):
	"""
	Get a value from the cache.

	Args:
		cache_name: The cache namespace (e.g., CACHE_INTEGRATION_SETTINGS)
		key: The cache key within the namespace
		account_id: Optional account ID for logging

	Returns:
		The cached value (dict/list) or None if not found or cache unavailable
	"""
	client = _get_redis_client()

	if client is None:
		return None

	full_key = _build_cache_key(cache_name, key)

	try:
		cached = client.get(full_key)

		if cached is not None:
			json_logger.debug(account_id, "Cache hit", cache_key=full_key)

			try:
				return json.loads(cached)

			except (json.JSONDecodeError, TypeError) as e:
				# JSON decode errors are per-key issues, not cache-wide outages
				json_logger.warning(account_id, "Cache decode error", cache_key=full_key, error=str(e))
				return None

		else:
			json_logger.debug(account_id, "Cache miss", cache_key=full_key)
			return None

	except Exception as e:
		json_logger.warning(account_id, "Cache read error", cache_key=full_key, error=str(e))
		_mark_cache_unavailable()
		return None


def put_in_cache(cache_name, key, value, ttl_seconds=None, account_id=None):
	"""
	Store a value in the cache.

	Args:
		cache_name: The cache namespace (e.g., CACHE_INTEGRATION_SETTINGS)
		key: The cache key within the namespace
		value: The value to cache (must be JSON serializable)
		ttl_seconds: TTL in seconds (default: CACHE_TTL_HOURS * 3600)
		account_id: Optional account ID for logging
	"""
	client = _get_redis_client()

	if client is None:
		return

	if value is None:
		return

	full_key = _build_cache_key(cache_name, key)

	if ttl_seconds is None or not isinstance(ttl_seconds, int) or ttl_seconds <= 0:
		ttl_seconds = CACHE_TTL_HOURS * 3600

	try:
		client.setex(full_key, ttl_seconds, json.dumps(value))
		json_logger.debug(account_id, "Cached value", cache_key=full_key, ttl_seconds=ttl_seconds)

	except Exception as e:
		json_logger.warning(account_id, "Cache write error", cache_key=full_key, error=str(e))
		_mark_cache_unavailable()


def evict_from_cache(cache_name, key, account_id=None):
	"""
	Remove a value from the cache.

	Args:
		cache_name: The cache namespace
		key: The cache key within the namespace
		account_id: Optional account ID for logging
	"""
	client = _get_redis_client()

	if client is None:
		return

	full_key = _build_cache_key(cache_name, key)

	try:
		client.delete(full_key)
		json_logger.debug(account_id, "Evicted from cache", cache_key=full_key)

	except Exception as e:
		json_logger.warning(account_id, "Cache evict error", cache_key=full_key, error=str(e))
		_mark_cache_unavailable()


def _mark_cache_unavailable():
	"""Mark cache as unavailable after an error to prevent repeated failed attempts."""
	global _cache_available

	if _cache_available:
		_cache_available = False
		json_logger.warning(None, "C4 SDK cache marked as unavailable - falling back to direct API calls")


def reset_cache_availability():
	"""Reset cache availability (e.g., for health checks or manual recovery)."""
	global _cache_available, _redis_client

	_cache_available = True
	_redis_client = None
	json_logger.info(None, "C4 SDK cache availability reset")


def is_caching_enabled():
	"""Check if caching is enabled and available."""
	return CACHE_ENABLED and _cache_available and CACHE_REDIS_HOST is not None


# Integration-specific cache functions

def get_integration_cache_key(account_id, integration_type):
	"""
	Build the cache key for an integration.
	Format: integration:{accountId}:{integrationType}
	"""
	return f"integration:{account_id}:{integration_type}"


def get_cached_integration(account_id, integration_type):
	"""
	Get a cached integration response.

	Args:
		account_id: The account ID
		integration_type: The integration type (e.g., 'TAI')

	Returns:
		The cached integration response or None
	"""
	key = get_integration_cache_key(account_id, integration_type)
	return get_from_cache(CACHE_INTEGRATION_SETTINGS, key, account_id=account_id)


def cache_integration(account_id, integration_type, response):
	"""
	Cache an integration response.

	Args:
		account_id: The account ID
		integration_type: The integration type (e.g., 'TAI')
		response: The integration response to cache
	"""
	key = get_integration_cache_key(account_id, integration_type)
	put_in_cache(CACHE_INTEGRATION_SETTINGS, key, response, account_id=account_id)


def evict_integration(account_id, integration_type):
	"""
	Evict a cached integration.

	Args:
		account_id: The account ID
		integration_type: The integration type (e.g., 'TAI')
	"""
	key = get_integration_cache_key(account_id, integration_type)
	evict_from_cache(CACHE_INTEGRATION_SETTINGS, key, account_id=account_id)
