"""
Configuration and shared utilities for Voicemode Server.

This module contains all configuration constants, global state, initialization functions,
and shared utilities used across the voicemode server.
"""

import os
import logging
import asyncio
import subprocess
from pathlib import Path
from typing import Dict, Optional
from datetime import datetime

# ==================== ENVIRONMENT CONFIGURATION ====================

# Debug configuration
DEBUG = os.getenv("VOICEMODE_DEBUG", "").lower() in ("true", "1", "yes", "on")
TRACE_DEBUG = os.getenv("VOICEMODE_DEBUG", "").lower() == "trace"
DEBUG_DIR = Path.home() / "voicemode_recordings"

# Audio saving configuration
SAVE_AUDIO = os.getenv("VOICEMODE_SAVE_AUDIO", "").lower() in ("true", "1", "yes", "on")
AUDIO_DIR = Path.home() / "voicemode_audio"

# Audio feedback configuration
audio_feedback_raw = os.getenv("VOICEMODE_AUDIO_FEEDBACK", "chime").lower()

# Backward compatibility: treat "true" as "chime", "false" as "none"
if audio_feedback_raw in ("true", "1", "yes", "on"):
    AUDIO_FEEDBACK_TYPE = "chime"
elif audio_feedback_raw in ("false", "0", "no", "off"):
    AUDIO_FEEDBACK_TYPE = "none"
elif audio_feedback_raw in ("chime", "voice", "both", "none"):
    AUDIO_FEEDBACK_TYPE = audio_feedback_raw
else:
    # Invalid value, default to chime
    AUDIO_FEEDBACK_TYPE = "chime"

# Derived boolean for compatibility
AUDIO_FEEDBACK_ENABLED = AUDIO_FEEDBACK_TYPE != "none"

# Voice feedback specific settings (used when type is "voice" or "both")
AUDIO_FEEDBACK_VOICE = os.getenv("VOICEMODE_FEEDBACK_VOICE", "nova")
AUDIO_FEEDBACK_MODEL = os.getenv("VOICEMODE_FEEDBACK_MODEL", "gpt-4o-mini-tts")
AUDIO_FEEDBACK_STYLE = os.getenv("VOICEMODE_FEEDBACK_STYLE", "whisper")  # "whisper" or "shout"

# Local provider preference configuration
PREFER_LOCAL = os.getenv("VOICEMODE_PREFER_LOCAL", "true").lower() in ("true", "1", "yes", "on")

# Auto-start configuration
AUTO_START_KOKORO = os.getenv("VOICEMODE_AUTO_START_KOKORO", "").lower() in ("true", "1", "yes", "on")

# Emotional TTS configuration
ALLOW_EMOTIONS = os.getenv("VOICEMODE_ALLOW_EMOTIONS", "false").lower() in ("true", "1", "yes", "on")
EMOTION_AUTO_UPGRADE = os.getenv("VOICEMODE_EMOTION_AUTO_UPGRADE", "false").lower() in ("true", "1", "yes", "on")

# ==================== SERVICE CONFIGURATION ====================

# OpenAI configuration
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is required")

# STT (Speech-to-Text) configuration
STT_BASE_URL = os.getenv("STT_BASE_URL", "https://api.openai.com/v1")
STT_MODEL = os.getenv("STT_MODEL", "whisper-1")

# TTS (Text-to-Speech) configuration
TTS_BASE_URL = os.getenv("TTS_BASE_URL", "https://api.openai.com/v1")
TTS_VOICE = os.getenv("TTS_VOICE", "alloy")
VOICEMODE_VOICES = os.getenv("VOICEMODE_VOICES", "af_sky,nova").split(",")
TTS_MODEL = os.getenv("TTS_MODEL", "tts-1")

# Provider-specific TTS configuration
OPENAI_TTS_BASE_URL = os.getenv("OPENAI_TTS_BASE_URL", "https://api.openai.com/v1")
KOKORO_TTS_BASE_URL = os.getenv("KOKORO_TTS_BASE_URL", os.getenv("TTS_BASE_URL", "http://localhost:8880/v1"))

# LiveKit configuration
LIVEKIT_URL = os.getenv("LIVEKIT_URL", "ws://localhost:7880")
LIVEKIT_API_KEY = os.getenv("LIVEKIT_API_KEY", "devkey")
LIVEKIT_API_SECRET = os.getenv("LIVEKIT_API_SECRET", "secret")

# ==================== AUDIO CONFIGURATION ====================

# Audio parameters
SAMPLE_RATE = 44100
CHANNELS = 1

# Audio format configuration
AUDIO_FORMAT = os.getenv("VOICEMODE_AUDIO_FORMAT", "opus").lower()
TTS_AUDIO_FORMAT = os.getenv("VOICEMODE_TTS_AUDIO_FORMAT", AUDIO_FORMAT).lower()
STT_AUDIO_FORMAT = os.getenv("VOICEMODE_STT_AUDIO_FORMAT", AUDIO_FORMAT).lower()

# Supported audio formats
SUPPORTED_AUDIO_FORMATS = ["opus", "mp3", "wav", "flac", "aac", "pcm"]

# Validate formats (validation messages will be logged after logger is initialized)
if AUDIO_FORMAT not in SUPPORTED_AUDIO_FORMATS:
    _invalid_audio_format = AUDIO_FORMAT
    AUDIO_FORMAT = "opus"

if TTS_AUDIO_FORMAT not in SUPPORTED_AUDIO_FORMATS:
    _invalid_tts_format = TTS_AUDIO_FORMAT
    TTS_AUDIO_FORMAT = AUDIO_FORMAT

if STT_AUDIO_FORMAT not in SUPPORTED_AUDIO_FORMATS:
    _invalid_stt_format = STT_AUDIO_FORMAT
    STT_AUDIO_FORMAT = AUDIO_FORMAT

# Format-specific quality settings
OPUS_BITRATE = int(os.getenv("VOICEMODE_OPUS_BITRATE", "32000"))  # Default 32kbps for voice
MP3_BITRATE = os.getenv("VOICEMODE_MP3_BITRATE", "64k")  # Default 64kbps
AAC_BITRATE = os.getenv("VOICEMODE_AAC_BITRATE", "64k")  # Default 64kbps

# ==================== STREAMING CONFIGURATION ====================

# Streaming playback configuration
STREAMING_ENABLED = os.getenv("VOICEMODE_STREAMING_ENABLED", "true").lower() in ("true", "1", "yes", "on")
STREAM_CHUNK_SIZE = int(os.getenv("VOICEMODE_STREAM_CHUNK_SIZE", "4096"))  # Download chunk size
STREAM_BUFFER_MS = int(os.getenv("VOICEMODE_STREAM_BUFFER_MS", "150"))  # Initial buffer before playback
STREAM_MAX_BUFFER = float(os.getenv("VOICEMODE_STREAM_MAX_BUFFER", "2.0"))  # Max buffer in seconds

# ==================== GLOBAL STATE ====================

# Service management
service_processes: Dict[str, subprocess.Popen] = {}

# Concurrency control for audio operations
# This prevents multiple audio operations from interfering with stdio
audio_operation_lock = asyncio.Lock()

# Flag to track if startup initialization has run
_startup_initialized = False

# ==================== LOGGING CONFIGURATION ====================

def setup_logging() -> logging.Logger:
    """Configure logging for the voice-mcp server.
    
    Returns:
        Logger instance configured for voice-mcp
    """
    log_level = logging.DEBUG if DEBUG else logging.INFO
    logging.basicConfig(
        level=log_level,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    logger = logging.getLogger("voicemode")
    
    # Trace logging setup
    if TRACE_DEBUG:
        import sys
        trace_file = Path.home() / "voicemode_trace.log"
        trace_logger = logging.getLogger("voicemode-trace")
        trace_handler = logging.FileHandler(trace_file, mode='a')
        trace_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
        trace_logger.addHandler(trace_handler)
        trace_logger.setLevel(logging.DEBUG)
        
        def trace_calls(frame, event, arg):
            if event == 'call':
                code = frame.f_code
                if 'voicemode' in code.co_filename or 'voice_mcp' in code.co_filename:
                    trace_logger.debug(f"Called {code.co_filename}:{frame.f_lineno} {code.co_name}")
            elif event == 'exception':
                trace_logger.debug(f"Exception: {arg}")
            return trace_calls
        
        sys.settrace(trace_calls)
        logger.info(f"Trace debugging enabled, writing to: {trace_file}")
    
    # Also log to file in debug mode
    if DEBUG:
        debug_log_file = Path.home() / "voicemode_debug.log"
        file_handler = logging.FileHandler(debug_log_file, mode='a')
        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        logger.addHandler(file_handler)
        logger.info(f"Debug logging to file: {debug_log_file}")
    
    # Suppress verbose binary data in HTTP logs
    if DEBUG:
        # Keep our debug logs but reduce HTTP client verbosity
        logging.getLogger("openai._base_client").setLevel(logging.INFO)
        logging.getLogger("httpcore").setLevel(logging.INFO)
        logging.getLogger("httpx").setLevel(logging.INFO)
    
    return logger

# ==================== DIRECTORY INITIALIZATION ====================

def initialize_directories():
    """Create necessary directories for debug and audio storage."""
    if DEBUG:
        DEBUG_DIR.mkdir(exist_ok=True)
    
    if SAVE_AUDIO:
        AUDIO_DIR.mkdir(exist_ok=True)

# ==================== UTILITY FUNCTIONS ====================

def get_debug_filename(prefix: str, extension: str) -> str:
    """Generate a timestamped filename for debug files.
    
    Args:
        prefix: Prefix for the filename (e.g., 'stt-input', 'tts-output')
        extension: File extension (e.g., 'wav', 'mp3')
    
    Returns:
        Timestamped filename string
    """
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
    return f"{prefix}_{timestamp}.{extension}"

# ==================== SOUNDDEVICE WORKAROUND ====================

def disable_sounddevice_stderr_redirect():
    """Comprehensively disable sounddevice's stderr redirection.
    
    This prevents sounddevice from redirecting stderr to /dev/null
    which can interfere with audio playback in MCP server context.
    """
    try:
        import sounddevice as sd
        import sys
        import atexit
        
        # Method 1: Override _ignore_stderr in various locations
        if hasattr(sd, '_sounddevice'):
            if hasattr(sd._sounddevice, '_ignore_stderr'):
                sd._sounddevice._ignore_stderr = lambda: None
        if hasattr(sd, '_ignore_stderr'):
            sd._ignore_stderr = lambda: None
        
        # Method 2: Override _check_error if it exists
        if hasattr(sd, '_check'):
            original_check = sd._check
            def safe_check(*args, **kwargs):
                # Prevent any stderr manipulation
                return original_check(*args, **kwargs)
            sd._check = safe_check
        
        # Method 3: Protect file descriptors
        original_stderr = sys.stderr
        
        # Create a hook to prevent stderr replacement
        def protect_stderr():
            if sys.stderr != original_stderr:
                sys.stderr = original_stderr
        
        # Install protection
        atexit.register(protect_stderr)
        
    except Exception as e:
        # Log but continue - audio might still work
        if DEBUG:
            # Can't use logger here as it's not initialized yet
            print(f"DEBUG: Could not fully disable sounddevice stderr redirect: {e}", file=sys.stderr)

# ==================== HTTP CLIENT CONFIGURATION ====================

# HTTP client configuration for OpenAI clients
HTTP_CLIENT_CONFIG = {
    'timeout': {
        'total': 30.0,
        'connect': 5.0
    },
    'limits': {
        'max_keepalive_connections': 5,
        'max_connections': 10
    }
}

# ==================== INITIALIZATION ====================

# Initialize directories on module import
initialize_directories()

# Apply sounddevice workaround on module import
disable_sounddevice_stderr_redirect()

# Set up logger
logger = setup_logging()

# Log any format validation warnings
if 'AUDIO_FORMAT' in locals() and '_invalid_audio_format' in locals():
    logger.warning(f"Unsupported audio format '{_invalid_audio_format}', falling back to 'opus'")

if 'TTS_AUDIO_FORMAT' in locals() and '_invalid_tts_format' in locals():
    logger.warning(f"Unsupported TTS audio format '{_invalid_tts_format}', falling back to '{AUDIO_FORMAT}'")

if 'STT_AUDIO_FORMAT' in locals() and '_invalid_stt_format' in locals():
    logger.warning(f"Unsupported STT audio format '{_invalid_stt_format}', falling back to '{AUDIO_FORMAT}'")

# ==================== AUDIO FORMAT UTILITIES ====================

def get_provider_supported_formats(provider: str, operation: str = "tts") -> list:
    """Get list of audio formats supported by a provider.
    
    Args:
        provider: Provider name (e.g., 'openai', 'kokoro', 'whisper-local')
        operation: 'tts' or 'stt'
    
    Returns:
        List of supported format strings
    """
    # Provider format capabilities
    # Based on API documentation and testing
    provider_formats = {
        # TTS providers
        "openai": {
            "tts": ["opus", "mp3", "aac", "flac", "wav", "pcm"],
            "stt": ["mp3", "opus", "wav", "flac", "m4a", "webm"]
        },
        "kokoro": {
            "tts": ["mp3", "wav"],  # May support more, needs verification
            "stt": []  # Kokoro is TTS only
        },
        # STT providers
        "whisper-local": {
            "tts": [],  # Whisper is STT only
            "stt": ["wav", "mp3", "opus", "flac", "m4a"]
        },
        "openai-whisper": {
            "tts": [],  # Whisper is STT only
            "stt": ["mp3", "opus", "wav", "flac", "m4a", "webm"]
        }
    }
    
    provider_info = provider_formats.get(provider, {})
    return provider_info.get(operation, [])


def validate_audio_format(format: str, provider: str, operation: str = "tts") -> str:
    """Validate and potentially adjust audio format based on provider capabilities.
    
    Args:
        format: Requested audio format
        provider: Provider name
        operation: 'tts' or 'stt'
    
    Returns:
        Valid format for the provider (may differ from requested)
    """
    supported = get_provider_supported_formats(provider, operation)
    
    if not supported:
        logger.warning(f"Provider '{provider}' does not support {operation} operation")
        return format
    
    if format in supported:
        return format
    
    # Fallback logic - prefer common formats
    fallback_order = ["opus", "mp3", "wav"]
    for fallback in fallback_order:
        if fallback in supported:
            logger.info(f"Format '{format}' not supported by {provider}, using '{fallback}' instead")
            return fallback
    
    # Last resort - use first supported format
    first_supported = supported[0]
    logger.warning(f"Using {provider}'s first supported format: {first_supported}")
    return first_supported


def get_audio_loader_for_format(format: str):
    """Get the appropriate AudioSegment loader for a format.
    
    Args:
        format: Audio format string
    
    Returns:
        AudioSegment method reference or None
    """
    from pydub import AudioSegment
    
    format_loaders = {
        "mp3": AudioSegment.from_mp3,
        "wav": AudioSegment.from_wav,
        "opus": AudioSegment.from_ogg,  # Opus uses OGG container
        "flac": AudioSegment.from_file if not hasattr(AudioSegment, 'from_flac') else AudioSegment.from_flac,
        "aac": AudioSegment.from_file,  # Generic loader for AAC
        "m4a": AudioSegment.from_file,  # Generic loader for M4A
        "webm": AudioSegment.from_file,  # Generic loader for WebM
        "ogg": AudioSegment.from_ogg,
        "pcm": AudioSegment.from_raw  # Requires additional parameters
    }
    
    return format_loaders.get(format)


def get_format_export_params(format: str) -> dict:
    """Get export parameters for a specific audio format.
    
    Args:
        format: Audio format string
    
    Returns:
        Dict with export parameters for pydub
    """
    params = {
        "format": format
    }
    
    if format == "mp3":
        params["bitrate"] = MP3_BITRATE
    elif format == "opus":
        # Opus in OGG container
        params["format"] = "opus"  # pydub uses 'opus' for OGG/Opus
        params["parameters"] = ["-b:a", str(OPUS_BITRATE)]
    elif format == "aac":
        params["bitrate"] = AAC_BITRATE
    elif format == "flac":
        # FLAC is lossless, no bitrate setting
        pass
    elif format == "wav":
        # WAV is uncompressed, no bitrate setting
        pass
    
    return params