#!/usr/bin/env python3

import os
import sys
import json
import typer
import hmac
import base64
import hashlib
import requests
from datetime import datetime, timezone, timedelta
from typing import Optional, List
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from meteopoint.config import Config

# Initialize app and console
app = typer.Typer(help="MeteoPoint - A beautiful weather CLI")
console = Console()

# Initialize config
config = Config()

def get_api_keys():
    """Get API keys from environment, returns None if not set"""
    return {
        "openweather": os.environ.get("OPENWEATHER_API_KEY"),
        "google_maps": os.environ.get("GOOGLE_MAPS_API_KEY"),
        "google_maps_secret": os.environ.get("GOOGLE_MAPS_SIGNING_SECRET")
    }

def get_cities_completion():
    """Get completion list for cities"""
    return config.get_all_aliases()

@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    city: str = typer.Argument(
        None,
        help="City name or alias",
        autocompletion=get_cities_completion
    ),
    format: str = typer.Option(
        None,
        "--format", "-f",
        help="Output format (pretty/json/minimal)",
        autocompletion=lambda: ["pretty", "json", "minimal"]
    ),
    fahrenheit: bool = typer.Option(
        False,
        "--fahrenheit",
        help="Show temperature in Fahrenheit"
    )
):
    """
    Get weather and environmental data for a specified city
    """
    # Show help if no command specified
    if not ctx.invoked_subcommand:
        if not city:
            console.print("[red]Error: No command or city specified[/red]")
            console.print("Run 'meteopoint --help' for usage")
            raise typer.Exit(1)

    # Get API keys
    api_keys = get_api_keys()
    
    # Check for API key before proceeding
    if not api_keys["openweather"]:
        console.print("[red]Error: OpenWeather API key is not set[/red]")
        console.print("[yellow]Set the OPENWEATHER_API_KEY environment variable to use MeteoPoint[/yellow]")
        sys.exit(1)

    if not city:
        # If no city, use default city
        city = config.get_default_city()
        if not city:
            console.print("[red]Error: No city specified and no default city set[/red]")
            return

    # Resolve city alias if it exists
    resolved_city = config.get_city(city)
    if resolved_city:
        city = resolved_city

    # Get output format
    output_format = format or config.get_format()
    
    # Get temperature unit
    use_fahrenheit = fahrenheit or config.get_temperature_unit() == "fahrenheit"

    try:
        # Fetch weather data
        weather_data = get_weather_data(city, api_keys)
        
        # Get coordinates for air quality data
        lat = weather_data["coord"]["lat"]
        lon = weather_data["coord"]["lon"]
        
        # Fetch air quality and forecast data
        aqi_data = get_air_quality(lat, lon, api_keys)
        forecast_data = get_forecast(lat, lon, api_keys)
        
        # Extract AQI information
        aqi_value, aqi_category, dominant_pollutant, last_updated, pollutants = get_aqi_from_response(aqi_data)
        
        # Prepare the display
        temp = weather_data["main"]["temp"]
        feels_like = weather_data["main"]["feels_like"]
        city_name = f"{weather_data['name']}, {weather_data['sys']['country']}"
        
        # Convert temperature if needed
        if use_fahrenheit:
            temp = (temp * 9/5) + 32
            feels_like = (feels_like * 9/5) + 32
            temp_unit = "°F"
        else:
            temp_unit = "°C"
        
        # Get weather emoji
        weather_code = weather_data["weather"][0]["id"]
        weather_emoji = get_weather_emoji(weather_code)
        
        # Format wind data
        wind_speed = weather_data["wind"]["speed"]
        wind_direction = get_wind_direction(weather_data["wind"].get("deg", 0))
        
        # Get sunrise/sunset times in local timezone
        timezone_offset = weather_data.get("timezone", 0)
        sunrise = format_time(weather_data["sys"]["sunrise"], timezone_offset)
        sunset = format_time(weather_data["sys"]["sunset"], timezone_offset)

        if output_format == "json":
            # Return machine-readable JSON
            result = {
                "city": weather_data["name"],
                "country": weather_data["sys"]["country"],
                "temperature": temp,
                "feels_like": feels_like,
                "humidity": weather_data["main"]["humidity"],
                "pressure": weather_data["main"]["pressure"],
                "wind": {
                    "speed": wind_speed,
                    "direction": wind_direction
                },
                "weather": weather_data["weather"][0]["description"],
                "air_quality": {
                    "index": aqi_value,
                    "category": aqi_category,
                    "dominant_pollutant": dominant_pollutant,
                    "pollutants": pollutants
                }
            }
            console.print_json(data=result)
        elif output_format == "minimal":
            # Return one-line summary
            console.print(f"{city_name}: {temp}{temp_unit}, {weather_data['weather'][0]['description']}, AQI: {aqi_value or 'N/A'}")
        else:
            # Format last updated time if it's a timestamp
            if isinstance(last_updated, int):
                last_updated = format_time(last_updated, timezone_offset)

            # Prepare forecast section
            forecast_text = ""
            if forecast_data and "list" in forecast_data:
                from datetime import datetime, timezone, timedelta
                forecast_text = "\n[bold]2-Day Forecast[/bold]"
                current_date = None
                tz = timezone(timedelta(seconds=timezone_offset))
                
                for item in forecast_data["list"]:
                    date = datetime.fromtimestamp(item["dt"], tz=timezone.utc).astimezone(tz)
                    if current_date != date.date():
                        current_date = date.date()
                        day_data = [x for x in forecast_data["list"] 
                                if datetime.fromtimestamp(x["dt"], tz=timezone.utc).astimezone(tz).date() == current_date]
                        
                        if len(day_data) > 0:  # Only process if we have data for the day
                            temp_min = min(x["main"]["temp"] for x in day_data)
                            temp_max = max(x["main"]["temp"] for x in day_data)
                            rain_prob = max((x.get("pop", 0) * 100) for x in day_data)
                            
                            if use_fahrenheit:
                                temp_min = (temp_min * 9/5) + 32
                                temp_max = (temp_max * 9/5) + 32
                            
                            emoji = get_weather_emoji(item["weather"][0]["id"])
                            forecast_text += f"\n• {date.strftime('%A')}: {emoji} {temp_min:.1f}{temp_unit} to {temp_max:.1f}{temp_unit} | Rain: {rain_prob:.0f}%"

            # Create the output panel
            output = f"""
{weather_emoji}  {city_name}

[bold]Temperature[/bold]
• Current: {temp:.1f}{temp_unit}
• Feels like: {feels_like:.1f}{temp_unit}
• Humidity: {weather_data['main']['humidity']}%
• Pressure: {weather_data['main']['pressure']} hPa

[bold]Wind[/bold]
• Speed: {wind_speed} m/s
• Direction: {wind_direction} ({weather_data["wind"].get("deg", 0)}°)

[bold]Air Quality[/bold]
• Index: {aqi_value if aqi_value else 'N/A'} ({aqi_category or 'N/A'})
{get_aqi_interpretation(float(aqi_value)) if aqi_value else ''}
• Dominant Pollutant: {dominant_pollutant or 'N/A'}
• Last Updated: {last_updated if last_updated else 'N/A'}
• Source: {aqi_data["source"].title() if aqi_data else 'N/A'}

[bold]Most Significant Pollutants[/bold]
{format_pollutant_display(pollutants)}

[bold]Daily Schedule[/bold]
• Sunrise: {sunrise}
• Sunset: {sunset}
• Weather: {weather_data['weather'][0]['description'].capitalize()}

[bold]2-Day Forecast[/bold]{forecast_text[len("[bold]2-Day Forecast[/bold]"):] if forecast_text else ''}
"""
            console.print(Panel(output.strip(), expand=False))

    except Exception as e:
        console.print(f"[red]Error: {str(e)}[/red]")

@app.command()
def save(
    city: str = typer.Argument(..., help="City name to save"),
    alias: str = typer.Option(None, "--alias", "-a", help="Alias for quick access")
):
    """Save a city with an optional alias for quick access"""
    api_keys = get_api_keys()
    
    if not api_keys["openweather"]:
        console.print("[red]Error: OpenWeather API key is not set[/red]")
        console.print("[yellow]Run 'meteopoint setup' to configure your API keys[/yellow]")
        sys.exit(1)
        
    try:
        # Verify city exists by fetching its weather
        weather_data = get_weather_data(city, api_keys)
        saved_alias = config.add_city(city, alias)
        console.print(f"[green]✓ Saved {city} with alias '{saved_alias}'[/green]")
    except Exception as e:
        console.print(f"[red]Error: Could not save city - {str(e)}[/red]")

@app.command()
def remove(alias: str = typer.Argument(..., help="Alias of the city to remove")):
    """Remove a saved city"""
    if config.remove_city(alias):
        console.print(f"[green]✓ Removed city with alias '{alias}'[/green]")
    else:
        console.print(f"[red]Error: No city found with alias '{alias}'[/red]")

@app.command()
def list():
    """List all saved cities"""
    cities = config.list_cities()
    if not cities:
        console.print("[yellow]No saved cities found. Use 'meteopoint save CITY' to add one.[/yellow]")
        return

    table = Table(title="Saved Cities")
    table.add_column("Alias", style="cyan")
    table.add_column("City", style="green")
    table.add_column("Default", style="yellow")

    default_city = config.get_default_city()
    for city in cities:
        is_default = "✓" if city["name"] == default_city else ""
        table.add_row(city["alias"], city["name"], is_default)

    console.print(table)

@app.command()
def config_cmd(
    format: str = typer.Option(None, "--format", "-f", help="Output format (pretty/json/minimal)"),
    unit: str = typer.Option(None, "--unit", "-u", help="Temperature unit (celsius/fahrenheit)"),
    default_city: str = typer.Option(None, "--default-city", "-d", help="Set default city")
):
    """Configure MeteoPoint settings"""
    api_keys = get_api_keys()
    
    if format:
        if format not in ["pretty", "json", "minimal"]:
            console.print("[red]Error: Format must be pretty, json, or minimal[/red]")
            return
        config.set_format(format)
        console.print(f"[green]✓ Set output format to {format}[/green]")

    if unit:
        if unit not in ["celsius", "fahrenheit"]:
            console.print("[red]Error: Unit must be celsius or fahrenheit[/red]")
            return
        config.set_temperature_unit(unit)
        console.print(f"[green]✓ Set temperature unit to {unit}[/green]")

    if default_city:
        try:
            # Verify city exists
            weather_data = get_weather_data(default_city, api_keys)
            config.set_default_city(default_city)
            console.print(f"[green]✓ Set default city to {default_city}[/green]")
        except Exception as e:
            console.print(f"[red]Error: Could not set default city - {str(e)}[/red]")

@app.command()
def install_completion():
    """Install shell completion for bash/zsh"""
    import subprocess
    shell = os.environ.get("SHELL", "").split("/")[-1]
    
    if shell == "bash":
        cmd = 'eval "$(register-python-argcomplete meteopoint)"'
        rc_file = os.path.expanduser("~/.bashrc")
    elif shell == "zsh":
        cmd = 'eval "$(register-python-argcomplete meteopoint)"'
        rc_file = os.path.expanduser("~/.zshrc")
    else:
        console.print(f"[red]Unsupported shell: {shell}[/red]")
        return

    with open(rc_file, "a") as f:
        f.write(f"\n# MeteoPoint completion\n{cmd}\n")
    
    console.print(f"[green]✓ Shell completion installed for {shell}[/green]")
    console.print("Please restart your shell or run:")
    console.print(f"source {rc_file}")

def sign_url(url: str, secret: str) -> str:
    """Sign a URL using the Google Maps signing secret"""
    if not secret:
        return url
    
    url_to_sign = url.split('?')[1].encode()
    decoded_key = base64.b64decode(secret.encode())
    signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1)
    encoded_signature = base64.b64encode(signature.digest()).decode()
    return f"{url}&signature={encoded_signature}"

def get_weather_data(city: str, api_keys: dict):
    """Fetch weather data for a given city"""
    base_url = "http://api.openweathermap.org/data/2.5/weather"
    params = {
        "q": city,
        "appid": api_keys["openweather"],
        "units": "metric"
    }
    
    try:
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        console.print(f"[red]Error fetching weather data: {str(e)}")
        sys.exit(1)

def get_air_quality(lat: float, lon: float, api_keys: dict):
    """Fetch air quality data using Google Maps Air Quality API with OpenWeather fallback"""
    # Try Google Maps Air Quality API first if available
    if api_keys["google_maps"]:
        base_url = "https://airquality.googleapis.com/v1/currentConditions:lookup"
        
        request_body = {
            "location": {
                "latitude": lat,
                "longitude": lon
            }
        }
        
        headers = {
            "Content-Type": "application/json",
            "X-Goog-Api-Key": api_keys["google_maps"]
        }
        
        try:
            response = requests.post(
                base_url,
                json=request_body,
                headers=headers
            )
            response.raise_for_status()
            data = response.json()
            
            # Always get detailed pollutant data from OpenWeather
            ow_base_url = "http://api.openweathermap.org/data/2.5/air_pollution"
            ow_params = {
                "lat": lat,
                "lon": lon,
                "appid": api_keys["openweather"]
            }
            
            try:
                ow_response = requests.get(ow_base_url, params=ow_params)
                ow_response.raise_for_status()
                return {
                    "source": "google",
                    "data": data,
                    "pollutants": ow_response.json()
                }
            except requests.RequestException:
                return {"source": "google", "data": data}
                
        except requests.RequestException:
            pass  # Fall through to OpenWeather
    
    # Fallback to OpenWeather Air Pollution API
    base_url = "http://api.openweathermap.org/data/2.5/air_pollution"
    params = {
        "lat": lat,
        "lon": lon,
        "appid": api_keys["openweather"]
    }
    
    try:
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        return {"source": "openweather", "data": response.json()}
    except requests.RequestException as e:
        console.print(f"[red]Error fetching air quality data: {str(e)}")
        return None

def get_forecast(lat: float, lon: float, api_keys: dict):
    """Fetch 2-day weather forecast"""
    base_url = "http://api.openweathermap.org/data/2.5/forecast"
    params = {
        "lat": lat,
        "lon": lon,
        "appid": api_keys["openweather"],
        "units": "metric",
        "cnt": 16  # Limit to next 2 days (8 measurements per day)
    }
    
    try:
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        console.print(f"[red]Error fetching forecast data: {str(e)}")
        return None

def get_who_guidelines():
    """Return WHO guidelines for pollutants"""
    return {
        "PM2.5": {"value": 10, "unit": "µg/m³"},  # Annual mean
        "PM10": {"value": 20, "unit": "µg/m³"},   # Annual mean
        "NO2": {"value": 40, "unit": "µg/m³"},    # Annual mean
        "O3": {"value": 100, "unit": "µg/m³"},    # 8-hour mean
        "SO2": {"value": 40, "unit": "µg/m³"},    # 24-hour mean
        "CO": {"value": 4, "unit": "mg/m³"},      # 24-hour mean
    }

def get_pollutant_health_impact(pollutant: str, value: float) -> tuple:
    """Get health impact level and color for a pollutant"""
    guidelines = get_who_guidelines()
    if pollutant not in guidelines:
        return "Unknown", "white"
    
    guideline = guidelines[pollutant]["value"]
    ratio = value / guideline
    
    if ratio <= 0.5:
        return "Safe", "green"
    elif ratio <= 1.0:
        return "Acceptable", "green"
    elif ratio <= 2.0:
        return "Moderate", "yellow"
    elif ratio <= 3.5:
        return "High", "red"
    else:
        return "Very High", "red"

def get_aqi_interpretation(aqi: float) -> str:
    """Get detailed AQI interpretation"""
    if aqi <= 25:
        return "[green]Excellent - Perfect for outdoor activities[/green]"
    elif aqi <= 50:
        return "[green]Good - Air quality is satisfactory[/green]"
    elif aqi <= 75:
        return "[yellow]Moderate - May affect sensitive individuals[/yellow]"
    elif aqi <= 100:
        return "[yellow]Fair - Sensitive groups may experience effects[/yellow]"
    elif aqi <= 150:
        return "[red]Poor - Everyone may experience health effects[/red]"
    else:
        return "[red]Very Poor - Avoid outdoor activities[/red]"

def format_pollutant_display(pollutants: dict) -> str:
    """Format pollutant display with color coding and WHO comparisons"""
    if not pollutants:
        return "No pollutant data available"
    
    # Calculate health impact for each pollutant
    pollutant_impacts = {}
    for name, data in pollutants.items():
        if name in get_who_guidelines():
            impact, color = get_pollutant_health_impact(name, data["value"])
            pollutant_impacts[name] = {
                "value": data["value"],
                "unit": data["unit"],
                "impact": impact,
                "color": color,
                "ratio": data["value"] / get_who_guidelines()[name]["value"]
            }
    
    # Sort by impact ratio and take top 5
    sorted_pollutants = sorted(
        pollutant_impacts.items(),
        key=lambda x: x[1]["ratio"],
        reverse=True
    )[:5]
    
    # Format the display
    lines = []
    for name, data in sorted_pollutants:
        color = data["color"]
        lines.append(
            f"• {name}: [{color}]{data['value']} {data['unit']} - {data['impact']}[/{color}]"
        )
    
    return "\n".join(lines)

def get_aqi_from_response(aqi_data: dict) -> tuple:
    """Extract AQI information from either Google or OpenWeather response"""
    if not aqi_data:
        return None, None, None, None, {}
    
    source = aqi_data["source"]
    data = aqi_data["data"]
    pollutants = {}
    
    # Get pollutant data from OpenWeather if available
    if source == "openweather" and "list" in data and data["list"]:
        components = data["list"][0]["components"]
        # Map component names to more readable format
        component_map = {
            "co": "CO",
            "no": "NO",
            "no2": "NO2",
            "o3": "O3",
            "so2": "SO2",
            "pm2_5": "PM2.5",
            "pm10": "PM10",
            "nh3": "NH3"
        }
        
        # Get all pollutant concentrations
        for code, value in components.items():
            if code in component_map:
                unit = "µg/m³" if code not in ["co"] else "mg/m³"
                pollutants[component_map[code]] = {
                    "value": value,
                    "unit": unit,
                    "display": f"{value} {unit}"
                }
    elif "pollutants" in aqi_data:
        components = aqi_data["pollutants"]["list"][0]["components"]
        # Map component names to more readable format
        component_map = {
            "co": "CO",
            "no": "NO",
            "no2": "NO2",
            "o3": "O3",
            "so2": "SO2",
            "pm2_5": "PM2.5",
            "pm10": "PM10",
            "nh3": "NH8"
        }
        
        # Get all pollutant concentrations
        for code, value in components.items():
            if code in component_map:
                unit = "µg/m³" if code not in ["co"] else "mg/m³"
                pollutants[component_map[code]] = {
                    "value": value,
                    "unit": unit,
                    "display": f"{value} {unit}"
                }
    
    if source == "google":
        if "indexes" in data:
            for index in data.get("indexes", []):
                if index.get("code") == "uaqi":
                    return (
                        index.get("aqi"),
                        index.get("category"),
                        index.get("dominantPollutant", "").upper(),
                        data.get("dateTime"),
                        pollutants
                    )
    elif source == "openweather":
        if "list" in data and data["list"]:
            aqi_level = data["list"][0]["main"]["aqi"]
            components = data["list"][0]["components"]
            
            # Get dominant pollutant (highest concentration relative to standard)
            dominant_pollutant = max(components.items(), key=lambda x: x[1])[0].upper()
            
            # Map OpenWeather AQI levels (1-5) to a 0-100 scale
            categories = {
                1: "Good air quality",
                2: "Fair air quality",
                3: "Moderate air quality",
                4: "Poor air quality",
                5: "Very poor air quality"
            }
            
            # Convert 1-5 scale to 0-100 scale
            aqi_value = ((aqi_level - 1) * 25)  # This maps 1->0, 2->25, 3->50, 4->75, 5->100
            
            return (
                aqi_value,
                categories.get(aqi_level, "Unknown"),
                dominant_pollutant,
                data["list"][0].get("dt"),
                pollutants
            )
    
    return None, None, None, None, {}

def get_weather_emoji(code: int) -> str:
    """Get appropriate weather emoji based on condition code"""
    if code == 800:  # Clear sky
        return "☀️"
    elif code == 801:  # Few clouds
        return "🌤️"
    elif code in [802, 803]:  # Scattered/broken clouds
        return "⛅"
    elif code == 804:  # Overcast clouds
        return "☁️"
    elif code >= 200 and code < 300:  # Thunderstorm
        return "⛈️"
    elif code >= 300 and code < 400:  # Drizzle
        return "🌧️"
    elif code >= 500 and code < 600:  # Rain
        return "🌧️"
    elif code >= 600 and code < 700:  # Snow
        return "🌨️"
    elif code >= 700 and code < 800:  # Atmosphere (fog, mist, etc)
        return "🌫️"
    return "🌡️"

def get_wind_direction(degrees: float) -> str:
    """Convert wind degrees to cardinal direction"""
    directions = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
                 "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]
    index = round(degrees / (360 / len(directions))) % len(directions)
    return directions[index]

def format_time(timestamp: int, timezone_offset: int) -> str:
    """Format Unix timestamp to local time string"""
    from datetime import datetime
    local_time = datetime.utcfromtimestamp(timestamp + timezone_offset)
    return local_time.strftime("%H:%M")

if __name__ == "__main__":
    app() 