"""Redis-based idempotency manager for payment processing."""

import logging
import json
from redis.cluster import RedisCluster
from typing import Optional, Dict, Any
from datetime import timedelta

logger = logging.getLogger(__name__)


class IdempotencyManager:
    """
    Redis-based idempotency manager for preventing duplicate payment processing.
    
    Uses ElastiCache Redis for fast, atomic idempotency checks with TTL.
    """
    
    def __init__(self, redis_endpoint: str, port: int = 6379, ssl: bool = True):
        """
        Initialize the IdempotencyManager with Redis connection.
        
        Args:
            redis_endpoint: ElastiCache Redis endpoint
            port: Redis port (default 6379)
            ssl: Use SSL connection (default False for Serverless Redis)
        """
        self.redis_endpoint = redis_endpoint
        self.port = port
        self.ssl = ssl
        
        try:
            logger.info(f"Attempting to connect to Redis: {redis_endpoint}:{port} (SSL={ssl})")
            
            # Connect to Redis
            self.redis_client = RedisCluster(
                host=redis_endpoint,
                port=port,
                ssl=True,
                ssl_cert_reqs=None,
                decode_responses=True,
                socket_timeout=5,
                socket_connect_timeout=5
            )
            
            # Test connection
            self.redis_client.ping()
            logger.info(f"✅ Successfully connected to Redis: {redis_endpoint}")
            
        except redis.ConnectionError as e:
            logger.error(f"❌ Redis ConnectionError: {e}")
            logger.error(f"   Endpoint: {redis_endpoint}:{port}")
            logger.error(f"   Check: 1) VPC/Subnet match, 2) Security groups, 3) Redis is running")
            self.redis_client = None
        except redis.TimeoutError as e:
            logger.error(f"❌ Redis TimeoutError: {e}")
            logger.error(f"   Endpoint: {redis_endpoint}:{port}")
            logger.error(f"   Check: Security group allows port {port} from EB")
            self.redis_client = None
        except Exception as e:
            logger.error(f"❌ Redis connection failed: {type(e).__name__}: {e}")
            logger.error(f"   Endpoint: {redis_endpoint}:{port}, SSL={ssl}")
            self.redis_client = None
    
    def generate_idempotency_key(self, payment_data: dict) -> str:
        """
        Generate idempotency key from payment data.
        
        Args:
            payment_data: Payment request data
            
        Returns:
            Unique idempotency key
        """
        import hashlib
        
        # Create a consistent string from payment data
        key_data = f"{payment_data.get('card_number', '')[-4:]}" \
                  f"{payment_data.get('amount', '')}" \
                  f"{payment_data.get('currency', '')}" \
                  f"{payment_data.get('email', '')}" \
                  f"{payment_data.get('cardholder_name', '')}"
        
        # Generate SHA-256 hash and prefix for namespacing
        hash_key = hashlib.sha256(key_data.encode()).hexdigest()
        return f"payment:idempotency:{hash_key}"
    
    def check_and_set_processing(self, idempotency_key: str, transaction_data: Dict[str, Any], 
                                ttl_hours: int = 24) -> Optional[Dict[str, Any]]:
        """
        Atomically check if payment is already processed and set as processing if not.
        
        Args:
            idempotency_key: Unique idempotency key
            transaction_data: Transaction data to store
            ttl_hours: TTL in hours for the idempotency key
            
        Returns:
            Existing transaction data if already processed, None if new
        """
        if not self.redis_client:
            logger.error("Redis client not available")
            return None
        
        try:
            # Check if key already exists
            existing_data = self.redis_client.get(idempotency_key)
            
            if existing_data:
                # Payment already processed or processing
                existing_transaction = json.loads(existing_data)
                logger.info(f"Idempotent request detected: {existing_transaction.get('transaction_id')}")
                return existing_transaction
            
            # Set as processing with TTL (atomic operation)
            processing_data = {
                **transaction_data,
                "status": "PROCESSING",
                "created_at": transaction_data.get("timestamp")
            }
            
            # Use SET with NX (only if not exists) and EX (expiry)
            ttl_seconds = ttl_hours * 3600
            success = self.redis_client.set(
                idempotency_key, 
                json.dumps(processing_data),
                nx=True,  # Only set if key doesn't exist
                ex=ttl_seconds  # Set expiry
            )
            
            if success:
                logger.info(f"Set idempotency key for processing: {idempotency_key}")
                return None  # New payment, proceed with processing
            else:
                # Race condition - another process set the key
                existing_data = self.redis_client.get(idempotency_key)
                if existing_data:
                    existing_transaction = json.loads(existing_data)
                    logger.info(f"Race condition detected, returning existing: {existing_transaction.get('transaction_id')}")
                    return existing_transaction
                
                return None
                
        except Exception as e:
            logger.error(f"Error in idempotency check: {e}")
            return None
    
    def update_transaction_status(self, idempotency_key: str, status: str, 
                                 additional_data: Optional[Dict[str, Any]] = None) -> bool:
        """
        Update the transaction status in Redis.
        
        Args:
            idempotency_key: Idempotency key
            status: New status (SUCCESS, FAILED, etc.)
            additional_data: Additional data to merge
            
        Returns:
            True if updated successfully, False otherwise
        """
        if not self.redis_client:
            logger.error("Redis client not available")
            return False
        
        try:
            # Get existing data
            existing_data = self.redis_client.get(idempotency_key)
            if not existing_data:
                logger.warning(f"Idempotency key not found for update: {idempotency_key}")
                return False
            
            # Update status
            transaction_data = json.loads(existing_data)
            transaction_data["status"] = status
            transaction_data["updated_at"] = transaction_data.get("timestamp")
            
            if additional_data:
                transaction_data.update(additional_data)
            
            # Update in Redis (preserve TTL)
            ttl = self.redis_client.ttl(idempotency_key)
            if ttl > 0:
                self.redis_client.set(idempotency_key, json.dumps(transaction_data), ex=ttl)
            else:
                self.redis_client.set(idempotency_key, json.dumps(transaction_data))
            
            logger.info(f"Updated idempotency status to {status}: {idempotency_key}")
            return True
            
        except Exception as e:
            logger.error(f"Error updating idempotency status: {e}")
            return False
    
    def get_transaction_status(self, idempotency_key: str) -> Optional[Dict[str, Any]]:
        """
        Get transaction status from Redis.
        
        Args:
            idempotency_key: Idempotency key
            
        Returns:
            Transaction data if found, None otherwise
        """
        if not self.redis_client:
            return None
        
        try:
            data = self.redis_client.get(idempotency_key)
            if data:
                return json.loads(data)
            return None
            
        except Exception as e:
            logger.error(f"Error getting transaction status: {e}")
            return None
    
    def cleanup_expired_keys(self) -> int:
        """
        Cleanup expired idempotency keys (Redis handles this automatically with TTL).
        This method is for manual cleanup if needed.
        
        Returns:
            Number of keys cleaned up
        """
        if not self.redis_client:
            return 0
        
        try:
            # Get all payment idempotency keys
            keys = self.redis_client.keys("payment:idempotency:*")
            expired_count = 0
            
            for key in keys:
                ttl = self.redis_client.ttl(key)
                if ttl == -1:  # No expiry set
                    # Set default expiry of 24 hours
                    self.redis_client.expire(key, 86400)
                elif ttl == -2:  # Key doesn't exist
                    expired_count += 1
            
            logger.info(f"Cleaned up {expired_count} expired idempotency keys")
            return expired_count
            
        except Exception as e:
            logger.error(f"Error during cleanup: {e}")
            return 0