"""
Barcode generation utilities for betting system
Supports major barcode standards used in retail and point-of-sale systems
"""

import logging
import base64
from typing import Optional, Tuple, Dict, Any
from io import BytesIO

logger = logging.getLogger(__name__)

# Supported barcode standards for retail/POS systems
BARCODE_STANDARDS = {
    'none': 'No Barcode',
    'code128': 'Code 128 (Alphanumeric)',
    'code39': 'Code 39 (Alphanumeric)', 
    'ean13': 'EAN-13 (13 digits)',
    'ean8': 'EAN-8 (8 digits)',
    'upca': 'UPC-A (12 digits)',
    'upce': 'UPC-E (8 digits)',
    'codabar': 'Codabar (Numeric)',
    'itf': 'ITF (Interleaved 2 of 5)'
}

def get_supported_barcode_standards() -> Dict[str, str]:
    """Get dictionary of supported barcode standards"""
    return BARCODE_STANDARDS.copy()

def validate_barcode_data(data: str, standard: str) -> bool:
    """
    Validate data for specific barcode standard
    
    Args:
        data: Data to encode in barcode
        standard: Barcode standard to validate against
        
    Returns:
        True if data is valid for the standard
    """
    if standard == 'none':
        return True
        
    if not data or not standard:
        return False
        
    # Remove any whitespace
    data = data.strip()
    
    try:
        if standard == 'code128':
            # Code 128 accepts alphanumeric characters
            return len(data) <= 48 and all(ord(c) >= 32 and ord(c) <= 126 for c in data)
            
        elif standard == 'code39':
            # Code 39 accepts uppercase letters, digits, and some symbols
            valid_chars = set('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%')
            return len(data) <= 43 and all(c in valid_chars for c in data.upper())
            
        elif standard == 'ean13':
            # EAN-13 requires exactly 12 digits (13th is check digit)
            return data.isdigit() and len(data) == 12
            
        elif standard == 'ean8':
            # EAN-8 requires exactly 7 digits (8th is check digit)
            return data.isdigit() and len(data) == 7
            
        elif standard == 'upca':
            # UPC-A requires exactly 11 digits (12th is check digit)
            return data.isdigit() and len(data) == 11
            
        elif standard == 'upce':
            # UPC-E requires exactly 7 digits (8th is check digit)
            return data.isdigit() and len(data) == 7
            
        elif standard == 'codabar':
            # Codabar accepts digits and some special characters
            valid_chars = set('0123456789-$:/.+ABCD')
            return len(data) <= 16 and all(c in valid_chars for c in data.upper())
            
        elif standard == 'itf':
            # ITF requires even number of digits
            return data.isdigit() and len(data) % 2 == 0 and len(data) <= 30
            
        else:
            return False
            
    except Exception as e:
        logger.error(f"Barcode validation error for {standard}: {e}")
        return False

def generate_barcode_image(data: str, standard: str, width: int = 300, height: int = 100) -> Optional[bytes]:
    """
    Generate barcode image as PNG bytes
    
    Args:
        data: Data to encode in barcode
        standard: Barcode standard (code128, code39, ean13, etc.)
        width: Image width in pixels
        height: Image height in pixels
        
    Returns:
        PNG image bytes or None if generation failed
    """
    if standard == 'none':
        return None
        
    try:
        # Validate data first
        if not validate_barcode_data(data, standard):
            logger.error(f"Invalid data '{data}' for barcode standard '{standard}'")
            return None
            
        # Try to import barcode library
        try:
            from barcode import get_barcode_class
            from barcode.writer import ImageWriter
        except ImportError:
            logger.error("Python barcode library not available. Install with: pip install python-barcode[images]")
            return None
            
        # Map our standard names to barcode library names
        standard_mapping = {
            'code128': 'code128',
            'code39': 'code39',
            'ean13': 'ean13',
            'ean8': 'ean8',
            'upca': 'upc',
            'upce': 'upce',
            'codabar': 'codabar',
            'itf': 'itf'
        }
        
        barcode_type = standard_mapping.get(standard)
        if not barcode_type:
            logger.error(f"Unsupported barcode standard: {standard}")
            return None
            
        # Get barcode class
        try:
            barcode_class = get_barcode_class(barcode_type)
        except Exception as e:
            logger.error(f"Failed to get barcode class for {barcode_type}: {e}")
            return None
            
        # Create image writer with custom options
        writer = ImageWriter()
        writer.set_options({
            'module_width': 0.2,
            'module_height': height / 100.0,
            'font_size': 10,
            'text_distance': 5.0,
            'background': 'white',
            'foreground': 'black',
            'write_text': True,
            'text': data
        })
        
        # Generate barcode
        barcode_instance = barcode_class(data, writer=writer)
        
        # Render to bytes
        buffer = BytesIO()
        barcode_instance.write(buffer)
        buffer.seek(0)
        
        image_bytes = buffer.getvalue()
        buffer.close()
        
        logger.debug(f"Generated {standard} barcode for data: {data}")
        return image_bytes
        
    except Exception as e:
        logger.error(f"Failed to generate barcode: {e}")
        return None

def generate_barcode_base64(data: str, standard: str, width: int = 300, height: int = 100) -> Optional[str]:
    """
    Generate barcode as base64 encoded string for embedding in HTML
    
    Args:
        data: Data to encode in barcode
        standard: Barcode standard
        width: Image width in pixels
        height: Image height in pixels
        
    Returns:
        Base64 encoded PNG string or None if generation failed
    """
    try:
        image_bytes = generate_barcode_image(data, standard, width, height)
        if image_bytes:
            encoded = base64.b64encode(image_bytes).decode('utf-8')
            return f"data:image/png;base64,{encoded}"
        return None
        
    except Exception as e:
        logger.error(f"Failed to generate base64 barcode: {e}")
        return None

def format_bet_id_for_barcode(bet_uuid: str, standard: str) -> str:
    """
    Format bet UUID for specific barcode standard
    
    Args:
        bet_uuid: Original bet UUID
        standard: Target barcode standard
        
    Returns:
        Formatted data suitable for the barcode standard
    """
    try:
        if standard == 'none':
            return bet_uuid
            
        # Remove hyphens and convert to uppercase
        clean_uuid = bet_uuid.replace('-', '').upper()
        
        if standard in ['code128', 'code39']:
            # These support alphanumeric, use full UUID for maximum uniqueness
            return clean_uuid
            
        elif standard in ['ean13', 'ean8', 'upca', 'upce', 'itf', 'codabar']:
            # These require numeric data
            # Convert hex UUID to numeric by taking hash
            import hashlib
            hash_obj = hashlib.md5(bet_uuid.encode())
            numeric_hash = str(int(hash_obj.hexdigest()[:12], 16))
            
            if standard == 'ean13':
                # EAN-13 needs exactly 12 digits
                return numeric_hash[:12].zfill(12)
            elif standard == 'ean8':
                # EAN-8 needs exactly 7 digits  
                return numeric_hash[:7].zfill(7)
            elif standard == 'upca':
                # UPC-A needs exactly 11 digits
                return numeric_hash[:11].zfill(11)
            elif standard == 'upce':
                # UPC-E needs exactly 7 digits
                return numeric_hash[:7].zfill(7)
            elif standard == 'codabar':
                # Codabar can use digits with start/stop characters
                return f"A{numeric_hash[:14]}A"
            elif standard == 'itf':
                # ITF needs even number of digits
                numeric_data = numeric_hash[:16].zfill(16)
                return numeric_data[:16] if len(numeric_data) % 2 == 0 else numeric_data[:15] + '0'
                
        return clean_uuid[:16]  # Fallback
        
    except Exception as e:
        logger.error(f"Failed to format bet ID for barcode: {e}")
        return bet_uuid[:16]  # Safe fallback

def create_bet_barcode_data(bet_uuid: str, barcode_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    """
    Create complete barcode data for a bet including image and metadata
    
    Args:
        bet_uuid: Bet UUID
        barcode_config: Barcode configuration from database
        
    Returns:
        Dictionary with barcode data or None if disabled
    """
    try:
        standard = barcode_config.get('standard', 'none')
        
        if standard == 'none':
            return None
            
        # Format bet ID for the barcode standard
        barcode_data = format_bet_id_for_barcode(bet_uuid, standard)
        
        # Generate barcode image
        barcode_base64 = generate_barcode_base64(barcode_data, standard)
        
        if not barcode_base64:
            logger.error(f"Failed to generate barcode image for bet {bet_uuid}")
            return None
            
        return {
            'standard': standard,
            'standard_name': BARCODE_STANDARDS.get(standard, standard),
            'data': barcode_data,
            'original_uuid': bet_uuid,
            'image_base64': barcode_base64,
            'width': barcode_config.get('width', 300),
            'height': barcode_config.get('height', 100)
        }
        
    except Exception as e:
        logger.error(f"Failed to create bet barcode data: {e}")
        return None

def reverse_lookup_bet_uuid(barcode_data: str, standard: str) -> Optional[str]:
    """
    Attempt to reverse lookup the original bet UUID from barcode data
    This is limited due to hash-based conversion for numeric standards
    
    Args:
        barcode_data: Scanned barcode data
        standard: Barcode standard used
        
    Returns:
        Possible bet UUID or None if cannot determine
    """
    try:
        if standard == 'none':
            return barcode_data
            
        if standard in ['code128', 'code39']:
            # For alphanumeric standards, we stored first 16 chars of clean UUID
            # Try to find matching bet in database by searching for UUIDs that match
            return None  # Requires database query
            
        else:
            # For numeric standards, we used MD5 hash conversion
            # Cannot reliably reverse this - need database lookup
            return None
            
    except Exception as e:
        logger.error(f"Failed to reverse lookup bet UUID: {e}")
        return None

def test_barcode_generation():
    """Test barcode generation for all supported standards"""
    test_results = {}
    test_uuid = "12345678-1234-5678-9abc-123456789def"
    
    for standard, name in BARCODE_STANDARDS.items():
        if standard == 'none':
            continue
            
        try:
            formatted_data = format_bet_id_for_barcode(test_uuid, standard)
            is_valid = validate_barcode_data(formatted_data, standard)
            
            if is_valid:
                image_bytes = generate_barcode_image(formatted_data, standard)
                success = image_bytes is not None
            else:
                success = False
                
            test_results[standard] = {
                'name': name,
                'test_data': formatted_data,
                'valid_data': is_valid,
                'generation_success': success,
                'error': None if success else 'Validation or generation failed'
            }
            
        except Exception as e:
            test_results[standard] = {
                'name': name,
                'test_data': None,
                'valid_data': False,
                'generation_success': False,
                'error': str(e)
            }
            
    return test_results