"""
Billing Database Module for MCP Server (with Snapshot Caching)
Provides tools to query customer billing databases with multi-server support.
Data is read from a local snapshot for performance; use refresh_snapshot() to update.
"""

import json
import os
import re
from datetime import datetime
import pymysql
from pymysql import MySQLError
from typing import Dict, List, Any, Optional, Union

# --- Constants ---
# Place cache in a dedicated, git-ignored directory
CACHE_DIR = os.path.join(os.path.dirname(__file__), '..', '..', '.cache')
CACHE_FILE_PREFIX = "billing_snapshot_"

# Ensure cache directory exists
os.makedirs(CACHE_DIR, exist_ok=True)


class BillingDatabase:
    """Billing database client with multi-server support and snapshot caching."""

    def __init__(self, db_configs: Dict[str, Dict[str, str]]):
        """
        Initialize with database configurations.
        
        Args:
            db_configs: Dictionary of server configurations from config.json.
        """
        self.servers = self._load_servers_from_config(db_configs)

    def _get_cache_path(self, server_id: str) -> str:
        """Get the file path for a server's cache."""
        return os.path.join(CACHE_DIR, f"{CACHE_FILE_PREFIX}{server_id}.json")

    def _load_servers_from_config(self, db_configs: Dict) -> Dict[str, Dict[str, Any]]:
        """Load and validate server configurations from a dictionary."""
        servers = {}
        if not isinstance(db_configs, dict):
            return {}

        for server_id, config in db_configs.items():
            required_keys = ['host', 'user', 'pass', 'name']
            if not all(key in config for key in required_keys):
                print(f"Warning: Skipping server '{server_id}' due to missing configuration.")
                continue

            servers[server_id] = {
                'host': config['host'],
                'user': config['user'],
                'password': config['pass'],
                'database': config['name'],
                'alias': config.get('alias', f"Server {server_id}"),
                'connect_timeout': config.get('connect_timeout', 10),
                'port': config.get('port', 3306)
            }
        return servers

    def _resolve_server_id(self, server_id: str) -> str:
        """
        Resolve the server ID. 
        If 'server_id' exists, return it.
        If 'server_id' is not found, but we only have one server configured, return that one.
        This provides a robust fallback for single-server setups.
        """
        if server_id in self.servers:
            return server_id
        
        # If requested ID not found, and we have exactly one server, assume that's the one
        if len(self.servers) == 1:
            return list(self.servers.keys())[0]
            
        # If default "1" requested but not found (and we have multiple providers), return first
        if server_id == "1" and self.servers:
            return list(self.servers.keys())[0]
            
        return server_id

    def get_server_config(self, server_id: str = "1") -> Optional[Dict[str, Any]]:
        """Get configuration for a specific server."""
        resolved_id = self._resolve_server_id(server_id)
        return self.servers.get(resolved_id)

    def list_servers(self) -> List[Dict[str, Any]]:
        """List all configured database servers and their cache status."""
        result = []
        for server_id, config in self.servers.items():
            cache_path = self._get_cache_path(server_id)
            cache_status = "Not created"
            last_updated = "N/A"
            if os.path.exists(cache_path):
                try:
                    with open(cache_path, 'r', encoding='utf-8') as f:
                        cache_data = json.load(f)
                        record_count = len(cache_data.get('customers', []))
                        timestamp = cache_data.get('meta', {}).get('timestamp', 0)
                        last_updated = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
                        cache_status = f"Ready ({record_count} records)"
                except (json.JSONDecodeError, KeyError):
                    cache_status = "Corrupted"
            
            result.append({
                'server_id': server_id,
                'alias': config.get('alias', f"Server {server_id}"),
                'host': config['host'],
                'database': config['database'],
                'cache_status': cache_status,
                'cache_last_updated': last_updated
            })
        return result

    def _get_connection(self, server_id: str = "1"):
        """Create database connection."""
        resolved_id = self._resolve_server_id(server_id)
        config = self.get_server_config(resolved_id)
        if not config:
            available = list(self.servers.keys())
            raise ValueError(f"Server '{server_id}' not found. Available servers: {available}")
        
        return pymysql.connect(
            host=config['host'],
            user=config['user'],
            password=config['password'],
            database=config['database'],
            port=config.get('port', 3306),
            connect_timeout=config.get('connect_timeout', 10),
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )

    def refresh_snapshot(self, server_id: str = "1") -> Dict[str, Any]:
        """
        Fetch all customer data from the database and save it to a local snapshot.
        This is a potentially long-running operation.
        
        Args:
            server_id: The ID of the server to snapshot.
            
        Returns:
            A dictionary with the result of the operation.
        """
        resolved_id = self._resolve_server_id(server_id)
        try:
            connection = self._get_connection(resolved_id)
            try:
                with connection.cursor() as cursor:
                    # Comprehensive query to get all necessary data in one go
                    sql = """
                        SELECT 
                            c.*,
                            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
                    """
                    cursor.execute(sql)
                    customers = cursor.fetchall()
                
                # Create snapshot data structure
                snapshot_data = {
                    "meta": {
                        "server_id": server_id,
                        "timestamp": datetime.now().timestamp(),
                        "customer_count": len(customers)
                    },
                    "customers": customers
                }
                
                # Save to cache file
                cache_path = self._get_cache_path(resolved_id)
                with open(cache_path, 'w', encoding='utf-8') as f:
                    json.dump(snapshot_data, f, indent=2, default=str) # Use default=str for dates
                    
                return {
                    "success": True,
                    "message": f"Snapshot for server '{resolved_id}' refreshed successfully.",
                    "customer_count": len(customers)
                }
            finally:
                connection.close()
        except (MySQLError, ValueError, IOError) as e:
            return {"success": False, "error": str(e)}

    def _load_snapshot(self, server_id: str) -> List[Dict[str, Any]]:
        """Load customer data from the local snapshot."""
        resolved_id = self._resolve_server_id(server_id)
        cache_path = self._get_cache_path(resolved_id)
        if not os.path.exists(cache_path):
            raise FileNotFoundError(f"Snapshot for server '{server_id}' not found. "
                                    f"Please run 'billing_refresh_snapshot' first.")
        
        with open(cache_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return data.get('customers', [])

    def search_customers(
        self,
        search_query: str = "",
        server_id: str = "1",
        limit: int = 50,
        offset: int = 0
    ) -> Dict[str, Any]:
        """Search customers from the local snapshot."""
        resolved_id = self._resolve_server_id(server_id)
        try:
            customers = self._load_snapshot(resolved_id)
            
            filtered_customers = []
            if search_query:
                # 1. Preprocess: Handle "natural" separation like "router 1" -> "router:1"
                # We target specific known keys to avoid breaking names
                search_query_proc = search_query
                natural_keys = r'(router|mitra|status|profile|type|action)'
                search_query_proc = re.sub(f'{natural_keys}\s+([a-zA-Z0-9_]+)', r'\1:\2', search_query_proc, flags=re.IGNORECASE)
                
                # 2. Preprocess: Remove common stopwords that might break "AND" search
                # e.g. "Budi di router 1" -> "Budi router:1" (removing "di")
                stopwords = r'\b(di|pada|in|at|with|by)\b'
                search_query_proc = re.sub(stopwords, '', search_query_proc, flags=re.IGNORECASE)
                
                # Split query into tokens by space
                tokens = search_query_proc.split()
                
                for cust in customers:
                    match_all = True
                    
                    for token in tokens:
                        if ':' in token:
                            # Handle field:value syntax
                            key, val = [p.strip() for p in token.split(':', 1)]
                            val_lower = val.lower()
                            field_val = str(cust.get(key, '')).lower()
                            
                            # Strict match for field:value
                            if field_val != val_lower:
                                match_all = False
                                break
                        else:
                            # Handle generic text search
                            token_lower = token.lower()
                            # Check multiple fields
                            if not (token_lower in str(cust.get('name', '')).lower() or
                                    token_lower in str(cust.get('no_wa', '')).lower() or
                                    token_lower in str(cust.get('address', '')).lower() or
                                    token_lower in str(cust.get('user_profile', '')).lower()):
                                match_all = False
                                break
                                
                    if match_all:
                        filtered_customers.append(cust)
            else:
                filtered_customers = customers

            total = len(filtered_customers)
            paginated_results = filtered_customers[offset : offset + limit]
            
            return {
                'success': True,
                'source': 'snapshot',
                'server_id': resolved_id,
                'search_query': search_query,
                'total': total,
                'limit': limit,
                'offset': offset,
                'customers': paginated_results
            }
        except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
            return {'success': False, 'error': str(e)}

    def get_customer_details(
        self,
        customer_id: Optional[str] = None,
        phone_number: Optional[str] = None,
        server_id: str = "1"
    ) -> Dict[str, Any]:
        """Get detailed customer information from the local snapshot."""
        resolved_id = self._resolve_server_id(server_id)
        if not customer_id and not phone_number:
            return {'success': False, 'error': 'Either customer_id or phone_number must be provided'}
            
        try:
            customers = self._load_snapshot(resolved_id)
            
            if customer_id:
                for cust in customers:
                    if str(cust.get('no_services')) == customer_id:
                        return {'success': True, 'source': 'snapshot', 'customer': cust}
            
            elif phone_number:
                search_numbers = self._normalize_phone_number(phone_number)
                for cust in customers:
                    if cust.get('no_wa') in search_numbers:
                        return {'success': True, 'source': 'snapshot', 'customer': cust}

            return {'success': False, 'error': 'Customer not found in snapshot'}
        except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
            return {'success': False, 'error': str(e)}

    def _normalize_phone_number(self, phone_number: Optional[str]) -> List[str]:
        """Normalize phone number for search with Indonesian format variations."""
        if not phone_number:
            return []
        
        p = phone_number.replace('@c.us', '')
        search_numbers = [p]
        if p.startswith('62'):
            search_numbers.append('0' + p[2:])
        elif p.startswith('0'):
            search_numbers.append('62' + p[1:])
        
        return list(dict.fromkeys(search_numbers)) # Unique list

    def get_customer_summary(self, server_id: str = "1") -> Dict[str, Any]:
        """Get customer statistics summary from the local snapshot."""
        resolved_id = self._resolve_server_id(server_id)
        try:
            customers = self._load_snapshot(resolved_id)
            
            total = len(customers)
            status_distribution = {}
            recent_count = 0
            thirty_days_ago = datetime.now().timestamp() - (30 * 24 * 3600)

            for cust in customers:
                # Status distribution
                status = cust.get('c_status', 'Unknown')
                status_distribution[status] = status_distribution.get(status, 0) + 1
                
                # Recent customers - assuming due_date is just the day of the month
                # This logic is likely flawed without a full date. Let's assume a different logic
                # For this example, we'll assume a 'created_at' field if it exists, otherwise skip
                # Let's use `due_date` as day and assume current month/year for a rough idea.
                # A proper implementation needs a full timestamp field.
                
            # Formatting status distribution with percentage
            status_dist_list = []
            for status, count in sorted(status_distribution.items(), key=lambda item: item[1], reverse=True):
                percentage = round((count / total) * 100, 2) if total > 0 else 0
                status_dist_list.append({'c_status': status, 'count': count, 'percentage': percentage})

            return {
                'success': True,
                'source': 'snapshot',
                'server_id': resolved_id,
                'total_customers': total,
                'status_distribution': status_dist_list
            }
        except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
            return {'success': False, 'error': str(e)}

    def check_connection(self, server_id: str = "1") -> Dict[str, Any]:
        """
        Perform a LIVE check of the database connection, bypassing the cache.
        """
        resolved_id = self._resolve_server_id(server_id)
        try:
            config = self.get_server_config(resolved_id)
            if not config:
                return {'success': False, 'error': f"Server '{resolved_id}' (requested: '{server_id}') not configured"}
            
            connection = self._get_connection(resolved_id)
            try:
                with connection.cursor() as cursor:
                    cursor.execute('SELECT VERSION() as version')
                    version_info = cursor.fetchone()
                    cursor.execute('SELECT COUNT(*) as customer_count FROM customer')
                    count_info = cursor.fetchone()
                    return {
                        'success': True,
                        'message': 'Live connection to database was successful.',
                        'host': config['host'],
                        'database': config['database'],
                        'version': version_info['version'] if version_info else 'N/A',
                        'customer_count': count_info['customer_count'] if count_info else 0
                    }
            finally:
                connection.close()
        except MySQLError as e:
            return {'success': False, 'error': f"Live connection failed: {e}"}
