import argparse
import yaml
import time
import logging
import subprocess
import sys
from datetime import datetime
from .models import AlertSeverity
from .location import LocationService
from .provider.factory import get_provider_instance
from .provider.astro import AstroProvider
from .narrator import Narrator
from .audio import AudioHandler

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("asl3_wx")

def wait_for_asterisk(timeout=120):
    """
    Polls Asterisk until it is ready to accept commands.
    """
    logger.info("Waiting for Asterisk to be fully booted...")
    start_time = time.time()
    while (time.time() - start_time) < timeout:
        try:
            # Check if Asterisk is running and accepting CLI commands
            # 'core waitfullybooted' ensures modules are loaded
            subprocess.run("sudo /usr/sbin/asterisk -rx 'core waitfullybooted'", 
                         shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            logger.info("Asterisk is ready.")
            return True
        except subprocess.CalledProcessError:
            time.sleep(2)
            
    logger.error("Timeout waiting for Asterisk.")
    return False

def load_config(path):
    with open(path, 'r') as f:
        return yaml.safe_load(f)

def setup_logging(config):
    """
    Configure logging based on config settings.
    """
    level = logging.INFO
    # If user wants debug, they can set it in config (not implemented yet, defaulting to INFO)
    logging.basicConfig(
        level=level,
        format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    # Silence noisy libs if needed
    logging.getLogger("requests").setLevel(logging.WARNING)
    logging.getLogger("urllib3").setLevel(logging.WARNING)

def do_full_report(config, report_config=None):
    loc_svc = LocationService(config)
    lat, lon = loc_svc.get_coordinates()
    
    # Provider
    prov_code = config.get('location', {}).get('provider')
    provider = get_provider_instance(CountryCode=prov_code, Lat=lat, Lon=lon, Config=config)
    
    # Auto-Timezone
    source = config.get('location', {}).get('source', 'fixed')
    cfg_tz = config.get('location', {}).get('timezone')
    
    # Fetch Data
    loc_info = provider.get_location_info(lat, lon)
    
    # Auto-Timezone Logic
    # 1. Prefer Provider's detected timezone (e.g. NWS provides it)
    if loc_info.timezone and loc_info.timezone not in ['UTC', 'Unknown']:
        logger.info(f"Using Provider Timezone: {loc_info.timezone}")
        if 'location' not in config: config['location'] = {}
        config['location']['timezone'] = loc_info.timezone
        
    # 2. Fallback to Config
    elif cfg_tz:
         logger.info(f"Using Config Timezone: {cfg_tz}")
         
    # 3. Last Resort: UTC (or maybe simple offset lookup if we implemented one)
    else:
        logger.warning("No timezone found! Defaulting to UTC.")
    
    # Manual City Override
    manual_city = config.get('location', {}).get('city')
    if manual_city:
        loc_info.city = manual_city
        
    logger.info(f"Resolved Location: {loc_info.city}, {loc_info.region} ({loc_info.country_code})")
    
    conditions = provider.get_conditions(lat, lon)
    forecast = provider.get_forecast(lat, lon)
    alerts = provider.get_alerts(lat, lon)
    
    # Astro
    astro = AstroProvider()
    sun_info = astro.get_astro_info(loc_info)
    
    # Narrate
    narrator = Narrator(config)
    text = narrator.build_full_report(loc_info, conditions, forecast, alerts, sun_info, report_config=report_config)
    logger.info(f"Report Text: {text}")
    
    # Audio
    handler = AudioHandler(config)
    wav_files = handler.generate_audio(text, "report.gsm")
    
    # Play
    nodes = config.get('voice', {}).get('nodes', [])
    handler.play_on_nodes(wav_files, nodes)

def monitor_loop(config):
    normal_interval = config.get('alerts', {}).get('check_interval_minutes', 10) * 60
    current_interval = normal_interval
    do_hourly = config.get('station', {}).get('hourly_report', False)
    
    known_alerts = set()
    
    # Initialize service objects
    loc_svc = LocationService(config)
    lat, lon = loc_svc.get_coordinates()
    prov_code = config.get('location', {}).get('provider')
    provider = get_provider_instance(CountryCode=prov_code, Lat=lat, Lon=lon, Config=config)
    narrator = Narrator(config)
    handler = AudioHandler(config)
    nodes = config.get('voice', {}).get('nodes', [])
    
    # Initialize AlertReady (Optional)
    ar_provider = None
    if config.get('alerts', {}).get('enable_alert_ready', False):
        try:
             # Lazy import
             from .provider.alert_ready import AlertReadyProvider
             ar_provider = AlertReadyProvider(alerts=config.get('alerts', {}))
             logger.info("AlertReady Provider Enabled.")
        except Exception as e:
             logger.error(f"Failed to load AlertReadyProvider: {e}")
             
    # Pre-fetch location info for portable callsign logic
    info = provider.get_location_info(lat, lon)
    logger.info(f"Monitor Location: {info.city}, {info.region}")

    logger.info(f"Starting Alert Monitor... (Hourly Reports: {do_hourly})")
    
    # Robust Wait for Asterisk
    if wait_for_asterisk():
        # Give a small buffer after it claims ready
        time.sleep(5) 
        
        try:
            # Startup Announcement
            # Resolve initial location info for city name
            # info variable is already fetched above
            city_name = info.city if info.city else "Unknown Location"
            interval_mins = int(normal_interval / 60)
            active_mins = config.get('alerts', {}).get('active_check_interval_minutes', 1)
            
            # Determine Source Name
            source_name = "National Weather Service" # Default fallback
            prov_class = provider.__class__.__name__
            
            if prov_class == 'ECProvider':
                source_name = "Environment Canada"
                if ar_provider: 
                    source_name += " and National Alert Ready System"
            elif prov_class == 'NWSProvider':
                 source_name = "National Weather Service"

            # Get Hourly Config
            hourly_cfg = config.get('station', {}).get('hourly_report', {})
            
            startup_text = narrator.get_startup_message(info, interval_mins, active_mins, nodes, source_name, hourly_config=hourly_cfg)
            logger.info(f"Startup Announcement: {startup_text}")
            
            # Generate and Play
            wav_files = handler.generate_audio(startup_text, filename="startup.gsm")
            if wav_files:
                handler.play_on_nodes(wav_files, nodes)
                
        except Exception as e:
            logger.error(f"Failed to play startup announcement: {e}")
            
    last_alert_check = 0
    last_report_hour = -1
    
    while True:
        now = time.time()
        now_dt = datetime.fromtimestamp(now)
        
        # 1. Hourly Report Check
        # Check config struct
        hr_config = config.get('station', {}).get('hourly_report', {})
        # Handle legacy boolean if present (though we updated config)
        if isinstance(hr_config, bool):
            hr_enabled = hr_config
            hr_minute = 0
            hr_content = None # Use default
        else:
            hr_enabled = hr_config.get('enabled', False)
            hr_minute = hr_config.get('minute', 0)
            hr_content = hr_config.get('content') # dict or None

        if hr_enabled:
            # Check if minute matches and we haven't run this hour yet
            # Note: We track last_report_hour. If minute is 15, we run at 10:15.
            # We need to ensure we don't run multiple times in the same hour.
            if now_dt.minute == hr_minute and now_dt.hour != last_report_hour:
                logger.info(f"Triggering Hourly Weather Report (Scheduled for XX:{hr_minute:02d})...")
                try:
                    do_full_report(config, report_config=hr_content)
                    last_report_hour = now_dt.hour
                except Exception as e:
                    logger.error(f"Hourly Report Failed: {e}")
        
        # 2. Alert Check
        if (now - last_alert_check) > current_interval:
            last_alert_check = now
            try:
                logger.info(f"Checking for alerts... (Interval: {current_interval}s)")
                # Fetch Weather Alerts
                alerts = provider.get_alerts(lat, lon)
                
                # Fetch AlertReady Alerts
                if ar_provider:
                    try:
                        ar_alerts = ar_provider.get_alerts(lat, lon)
                        if ar_alerts:
                             logger.info(f"AlertReady found {len(ar_alerts)} items.")
                             alerts.extend(ar_alerts)
                    except Exception as e:
                        logger.error(f"AlertReady Check Failed: {e}")
                
                current_ids = {a.id for a in alerts}
                
                new_alerts = []
                for a in alerts:
                    if a.id not in known_alerts:
                        new_alerts.append(a)
                        known_alerts.add(a.id)
                
                # Check for Tone Trigger & Dynamic Polling
                tone_config = config.get('alerts', {}).get('alert_tone', {})
                ranks = {'Unknown': 0, 'Advisory': 1, 'Watch': 2, 'Warning': 3, 'Critical': 4}
                
                # 1. Determine Max Severity of ALL active alerts for Dynamic Polling
                max_severity_rank = 0
                for a in alerts: # Scan ALL active alerts
                     s_val = a.severity.value if hasattr(a.severity, 'value') else str(a.severity)
                     rank = ranks.get(s_val, 0)
                     if rank > max_severity_rank:
                         max_severity_rank = rank
                         
                # If Watch (2) or Higher, set interval to configured active interval
                new_interval = normal_interval
                active_threat = False
                
                if max_severity_rank >= 2:
                    # Default to 1 minute if not set
                    active_mins = config.get('alerts', {}).get('active_check_interval_minutes', 1)
                    new_interval = active_mins * 60
                    active_threat = True
                    logger.info(f"Active Watch/Warning/Critical detected. Requesting {active_mins}-minute polling.")
                
                # Check for Interval Change
                if new_interval != current_interval:
                    logger.info(f"Interval Change Detected: {current_interval} -> {new_interval}")
                    current_interval = new_interval
                    
                    mins = int(current_interval / 60)
                    msg = narrator.get_interval_change_message(mins, active_threat, loc=info)
                    logger.info(f"Announcing Interval Change: {msg}")
                    
                    try:
                        wavs = handler.generate_audio(msg, "interval_change.gsm")
                        handler.play_on_nodes(wavs, nodes)
                    except Exception as e:
                        logger.error(f"Failed to announce interval change: {e}")

                # 2. Check for Tone Trigger (New Alerts Only)
                if new_alerts and tone_config.get('enabled', False):
                    min_sev_str = tone_config.get('min_severity', 'Warning')
                    threshold = ranks.get(min_sev_str, 3)
                    
                    should_tone = False
                    for a in new_alerts:
                        s_val = a.severity.value if hasattr(a.severity, 'value') else str(a.severity)
                        if ranks.get(s_val, 0) >= threshold:
                            should_tone = True
                            break
                    
                    if should_tone:
                        logger.info("High severity alert detected. Playing Attention Signal.")
                        try:
                            tone_file = handler.ensure_alert_tone()
                            handler.play_on_nodes([tone_file], nodes)
                        except Exception as e:
                            logger.error(f"Failed to play alert tone: {e}")

                # Announce new items
                if new_alerts:
                    logger.info(f"New Alerts detected: {len(new_alerts)}")
                    text = narrator.announce_alerts(new_alerts, loc=info)
                    wavs = handler.generate_audio(text, "alert.gsm")
                    handler.play_on_nodes(wavs, nodes)
                
                # Cleanup expired from known
                known_alerts = known_alerts.intersection(current_ids)
                
            except Exception as e:
                logger.error(f"Monitor error: {e}")
        
        # Check every minute (resolution of the loop)
        time.sleep(60)

def do_test_alert(config):
    """
    Executes the comprehensive Test Alert sequence.
    """
    logger.info("Executing System Test Alert...")
    
    narrator = Narrator(config)
    handler = AudioHandler(config)
    nodes = config.get('voice', {}).get('nodes', [])

    if not nodes:
        logger.error("No nodes configured for playback.")
        return

    # Resolve Location for Callsign Logic
    loc_svc = LocationService(config)
    lat, lon = loc_svc.get_coordinates()
    prov_code = config.get('location', {}).get('provider')
    provider = get_provider_instance(CountryCode=prov_code, Lat=lat, Lon=lon, Config=config)
    loc_info = provider.get_location_info(lat, lon)

    # 1. Preamble
    logger.info("Playing Preamble...")
    preamble_text = narrator.get_test_preamble(loc=loc_info)
    files = handler.generate_audio(preamble_text, "test_preamble.gsm")
    handler.play_on_nodes(files, nodes)
    
    # 2. Silence (10s unkeyed)
    logger.info("Waiting 10s (Unkeyed)...")
    time.sleep(10)
    
    # 3. Alert Tone
    # Check if tone is enabled in config, otherwise force it for test? 
    # User said "The preceding tone" in postamble, so we should play it regardless of config setting for the TEST mode.
    logger.info("Playing Alert Tone...")
    tone_file = handler.ensure_alert_tone()
    handler.play_on_nodes([tone_file], nodes)
    
    # 4. Test Message
    logger.info("Playing Test Message...")
    msg_text = narrator.get_test_message()
    files = handler.generate_audio(msg_text, "test_message.gsm")
    handler.play_on_nodes(files, nodes)
    
    # 5. Postamble
    logger.info("Playing Postamble...")
    post_text = narrator.get_test_postamble(loc=loc_info)
    files = handler.generate_audio(post_text, "test_postamble.gsm")
    handler.play_on_nodes(files, nodes)
    
    logger.info("Test Alert Concluded.")

def main():
    parser = argparse.ArgumentParser(description="ASL3 Weather Announcer")
    parser.add_argument("--config", default="config.yaml", help="Path to config file")
    parser.add_argument("--report", action="store_true", help="Run immediate full weather report")
    parser.add_argument("--monitor", action="store_true", help="Run in continuous monitor mode")
    parser.add_argument("--test-alert", action="store_true", help="Run a comprehensive Test Alert sequence")
    
    args = parser.parse_args()
    config = load_config(args.config)
    # setup_logging(config) - removed double call, typically called once? 
    # Ah, setup_logging was called in my previous version.
    
    if args.test_alert:
        do_test_alert(config)
        sys.exit(0)
    elif args.report:
        # Pass the hourly report content config if available so manual run matches scheduled run
        hr_config = config.get('station', {}).get('hourly_report', {})
        if isinstance(hr_config, bool):
             content = None
        else:
             content = hr_config.get('content')
             
        do_full_report(config, report_config=content)
        sys.exit(0)
    elif args.monitor:
        monitor_loop(config)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()
