"""
Customer Database Module - Python Version (Async)
Replacement for services/customerDb.js

Provides async functions to query customer billing database with connection pooling
and security checks for read-only operations.
Uses aiomysql for async MySQL access.
"""

import os
import re
import asyncio
import sys
from typing import Optional, List, Dict, Any, Union
from dotenv import load_dotenv

# Try to import aiomysql, but provide fallback
try:
    import aiomysql
    AIOMYSQL_AVAILABLE = True
except ImportError:
    AIOMYSQL_AVAILABLE = False
    print("Warning: aiomysql not installed. Install with: pip install aiomysql", file=sys.stderr)

# Load environment variables
load_dotenv()

# Database configuration from environment
DB_CONFIG = {
    'host': os.getenv('BILLING_DB_HOST'),
    'user': os.getenv('BILLING_DB_USER'),
    'password': os.getenv('BILLING_DB_PASS'),
    'db': os.getenv('BILLING_DB_NAME'),
    'charset': 'utf8mb4',
    'autocommit': True,
    'minsize': 1,
    'maxsize': 10,
}

# Global connection pool
_pool = None

async def get_pool():
    """Get or create connection pool"""
    global _pool
    
    if not AIOMYSQL_AVAILABLE:
        raise RuntimeError("aiomysql is not installed. Please install with: pip install aiomysql")
    
    if _pool is None:
        # Validate config
        required = ['host', 'user', 'password', 'db']
        for key in required:
            if not DB_CONFIG.get(key):
                raise ValueError(f"Missing database configuration: {key}")
        
        _pool = await aiomysql.create_pool(**DB_CONFIG)
    
    return _pool

async def close_pool():
    """Close connection pool"""
    global _pool
    if _pool:
        _pool.close()
        await _pool.wait_closed()
        _pool = None

def normalize_phone_number(phone_number: str) -> List[str]:
    """
    Normalize phone number for search with Indonesian format variations.
    
    Args:
        phone_number: Input phone number (e.g., "62812345678", "0812345678", "62812345678@c.us")
    
    Returns:
        List of possible phone number formats to search for
    """
    # Remove @c.us suffix if present
    if phone_number.endswith('@c.us'):
        phone_number = phone_number.replace('@c.us', '')
    
    search_numbers = [phone_number]
    
    # Handle Indonesian phone number format variations
    if phone_number.startswith('62'):
        # Convert 62... to 0...
        search_numbers.append('0' + phone_number[2:])
    elif phone_number.startswith('0'):
        # Convert 0... to 62...
        search_numbers.append('62' + phone_number[1:])
    
    # Remove duplicates while preserving order
    seen = set()
    unique_numbers = []
    for num in search_numbers:
        if num not in seen:
            seen.add(num)
            unique_numbers.append(num)
    
    return unique_numbers

async def get_customer_by_phone(phone_number: str) -> Optional[Dict[str, Any]]:
    """
    Get customer details by phone number with format normalization.
    
    Args:
        phone_number: Phone number to search for
    
    Returns:
        Customer dictionary or None if not found
    """
    try:
        search_numbers = normalize_phone_number(phone_number)
        
        query = """
            SELECT c.name, c.user_profile, c.c_status, c.due_date, c.address, c.no_services,
                   pi.name as package_name, pi.price as package_price, pi.description as package_description
            FROM customer c
            LEFT JOIN services s ON c.no_services = s.no_services
            LEFT JOIN package_item pi ON s.item_id = pi.p_item_id
            WHERE c.no_wa IN ({})
            LIMIT 1
        """
        
        # Create placeholders for IN clause
        placeholders = ','.join(['%s'] * len(search_numbers))
        formatted_query = query.format(placeholders)
        
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute(formatted_query, search_numbers)
                rows = await cursor.fetchall()
                
                if rows:
                    return rows[0]
                return None
                
    except Exception as e:
        print(f"Error in get_customer_by_phone: {e}", file=sys.stderr)
        return None

async def test_connection() -> bool:
    """Test database connection"""
    try:
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor() as cursor:
                await cursor.execute('SELECT 1')
                result = await cursor.fetchone()
                return result is not None
    except Exception as e:
        print(f"Database connection test failed: {e}", file=sys.stderr)
        return False

async def search_customer(query: str, limit: int = 5) -> List[Dict[str, Any]]:
    """
    Search customers by name, phone, status, or profile.
    
    Args:
        query: Search term
        limit: Maximum results to return
    
    Returns:
        List of customer dictionaries
    """
    try:
        search_term = f"%{query}%"
        sql = """
            SELECT name, user_profile, c_status, due_date, address, no_services, no_wa
            FROM customer 
            WHERE name LIKE %s OR no_wa LIKE %s OR c_status LIKE %s OR user_profile LIKE %s
            LIMIT %s
        """
        
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute(sql, (search_term, search_term, search_term, search_term, limit))
                return await cursor.fetchall()
                
    except Exception as e:
        print(f"Database Search Error: {e}", file=sys.stderr)
        return []

async def get_summary() -> List[Dict[str, Any]]:
    """Get customer count grouped by status"""
    try:
        sql = "SELECT c_status, COUNT(*) as total FROM customer GROUP BY c_status"
        
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute(sql)
                return await cursor.fetchall()
                
    except Exception as e:
        print(f"DB Summary Error: {e}", file=sys.stderr)
        return []

def _validate_read_only_sql(sql: str) -> bool:
    """
    Validate SQL for read-only operations with security checks.
    
    Args:
        sql: SQL query to validate
    
    Returns:
        True if valid, raises ValueError if invalid
    """
    # Normalize for checking
    normalized_sql = sql.strip().upper()
    
    # 1. Must start with SELECT
    if not normalized_sql.startswith('SELECT'):
        raise ValueError("Security Violation: Only SELECT queries are allowed.")
    
    # 2. Check for prohibited keywords with word boundaries
    prohibited = [
        'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE',
        'REPLACE', 'CREATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE',
        'MERGE', 'LOCK', 'UNLOCK', 'COMMIT', 'ROLLBACK'
    ]
    
    for keyword in prohibited:
        # Use regex for word boundary check
        pattern = rf'\b{keyword}\b'
        if re.search(pattern, normalized_sql, re.IGNORECASE):
            raise ValueError(f"Security Violation: Keyword '{keyword}' is prohibited.")
    
    # 3. Check for semicolons (multiple statements)
    if ';' in sql:
        raise ValueError("Security Violation: Multiple statements (;) are not allowed.")
    
    # 4. Additional safety: limit query length (optional)
    if len(sql) > 10000:
        raise ValueError("Security Violation: Query too long.")
    
    return True

async def execute_read_only_query(sql: str) -> Union[List[Dict[str, Any]], Dict[str, str]]:
    """
    Execute a read-only SQL query with security checks.
    
    Args:
        sql: SELECT SQL query to execute
    
    Returns:
        Query results as list of dictionaries, or error dictionary
    """
    try:
        # Validate SQL security
        _validate_read_only_sql(sql)
        
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute(sql)
                rows = await cursor.fetchall()
                return rows
                
    except ValueError as e:
        print(f"SQL Security Validation Error: {e}", file=sys.stderr)
        return {'error': str(e)}
    except Exception as e:
        print(f"SQL Execution Error: {e}", file=sys.stderr)
        return {'error': str(e)}

async def get_search_count(query: str) -> int:
    """
    Count customers matching search query.
    
    Args:
        query: Search term
    
    Returns:
        Count of matching customers
    """
    try:
        search_term = f"%{query}%"
        sql = """
            SELECT COUNT(*) as total 
            FROM customer 
            WHERE name LIKE %s OR no_wa LIKE %s OR c_status LIKE %s OR user_profile LIKE %s OR address LIKE %s
        """
        
        pool = await get_pool()
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute(sql, (search_term, search_term, search_term, search_term, search_term))
                result = await cursor.fetchone()
                return result['total'] if result else 0
                
    except Exception as e:
        print(f"DB Count Error: {e}", file=sys.stderr)
        return 0

# For backward compatibility with existing code (camelCase aliases)
async def getCustomerByPhone(phoneNumber: str) -> Optional[Dict[str, Any]]:
    """Alias for get_customer_by_phone (camelCase for compatibility)"""
    return await get_customer_by_phone(phoneNumber)

async def searchCustomer(query: str, limit: int = 5) -> List[Dict[str, Any]]:
    """Alias for search_customer"""
    return await search_customer(query, limit)

async def getSummary() -> List[Dict[str, Any]]:
    """Alias for get_summary"""
    return await get_summary()

async def getSearchCount(query: str) -> int:
    """Alias for get_search_count"""
    return await get_search_count(query)

async def testConnection() -> bool:
    """Alias for test_connection"""
    return await test_connection()

async def executeReadOnlyQuery(sql: str) -> Union[List[Dict[str, Any]], Dict[str, str]]:
    """Alias for execute_read_only_query"""
    return await execute_read_only_query(sql)

# Synchronous wrappers for non-async contexts (using asyncio.run)
def get_customer_by_phone_sync(phone_number: str) -> Optional[Dict[str, Any]]:
    """Synchronous wrapper for get_customer_by_phone"""
    return asyncio.run(get_customer_by_phone(phone_number))

def search_customer_sync(query: str, limit: int = 5) -> List[Dict[str, Any]]:
    """Synchronous wrapper for search_customer"""
    return asyncio.run(search_customer(query, limit))

def execute_read_only_query_sync(sql: str) -> Union[List[Dict[str, Any]], Dict[str, str]]:
    """Synchronous wrapper for execute_read_only_query"""
    return asyncio.run(execute_read_only_query(sql))

# Cleanup on module unload
import atexit
import asyncio

@atexit.register
def cleanup():
    """Cleanup pool on exit"""
    if _pool:
        try:
            # Try to run cleanup in existing event loop or create new one
            loop = asyncio.get_event_loop()
            if loop.is_running():
                # Schedule cleanup but don't wait
                asyncio.create_task(close_pool())
            else:
                loop.run_until_complete(close_pool())
        except RuntimeError:
            # No event loop, create new one
            asyncio.run(close_pool())

# Command Line Interface
if __name__ == '__main__':
    import argparse
    import json
    import sys
    
    async def run_command():
        parser = argparse.ArgumentParser(description='Customer Database CLI')
        parser.add_argument('--function', required=True, 
                          choices=['getCustomerByPhone', 'searchCustomer', 'getSummary', 
                                   'getSearchCount', 'testConnection', 'executeReadOnlyQuery'],
                          help='Function to execute')
        parser.add_argument('--phone', help='Phone number for getCustomerByPhone')
        parser.add_argument('--query', help='Search query for searchCustomer/getSearchCount')
        parser.add_argument('--limit', type=int, default=5, help='Limit for searchCustomer')
        parser.add_argument('--sql', help='SQL query for executeReadOnlyQuery')
        parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds')
        
        args = parser.parse_args()
        
        try:
            result = None
            
            if args.function == 'getCustomerByPhone':
                if not args.phone:
                    print(json.dumps({'error': 'Missing --phone parameter'}))
                    sys.exit(1)
                result = await get_customer_by_phone(args.phone)
                
            elif args.function == 'searchCustomer':
                if not args.query:
                    print(json.dumps({'error': 'Missing --query parameter'}))
                    sys.exit(1)
                result = await search_customer(args.query, args.limit)
                
            elif args.function == 'getSummary':
                result = await get_summary()
                
            elif args.function == 'getSearchCount':
                if not args.query:
                    print(json.dumps({'error': 'Missing --query parameter'}))
                    sys.exit(1)
                result = await get_search_count(args.query)
                
            elif args.function == 'testConnection':
                result = await test_connection()
                
            elif args.function == 'executeReadOnlyQuery':
                if not args.sql:
                    print(json.dumps({'error': 'Missing --sql parameter'}))
                    sys.exit(1)
                result = await execute_read_only_query(args.sql)
            
            # Ensure result is JSON serializable
            if isinstance(result, dict) and 'error' in result:
                print(json.dumps(result))
                sys.exit(1)
            else:
                print(json.dumps(result))
                sys.exit(0)
                
        except Exception as e:
            print(json.dumps({'error': str(e)}))
            sys.exit(1)
    
    # Run async command
    asyncio.run(run_command())