#!/usr/bin/env python3
"""
Proximity Aircraft Alert System
Tracks a specific aircraft (by ICAO code) and alerts when it's near your location.
Usage: python proximity_alert.py <zipcode>
"""

import sys
import time
import requests
from math import radians, cos, sin, asin, sqrt
from datetime import datetime

# Configuration
ICAO_CODE = "A835AF"  # Elon Musk's jet - change this to track different aircraft
PROXIMITY_RADIUS_KM = 80  # Alert when within 80km
CHECK_INTERVAL_SECONDS = 30  # How often to check
ALERT_COOLDOWN_SECONDS = 300  # Don't spam alerts (5 min cooldown)

class ProximityAlert:
    def __init__(self, zipcode, icao_code, radius_km):
        self.zipcode = zipcode
        self.icao_code = icao_code.lower()
        self.radius_km = radius_km
        self.user_lat = None
        self.user_lon = None
        self.last_alert_time = 0
        self.currently_in_range = False

    def get_coordinates_from_zip(self):
        """Convert zip code to lat/lon using free API"""
        try:
            # Using free zippopotam.us API
            response = requests.get(f"http://api.zippopotam.us/us/{self.zipcode}", timeout=10)
            if response.status_code == 200:
                data = response.json()
                self.user_lat = float(data['places'][0]['latitude'])
                self.user_lon = float(data['places'][0]['longitude'])
                print(f"📍 Location set to {data['places'][0]['place name']}, {data['places'][0]['state abbreviation']}")
                print(f"   Coordinates: {self.user_lat}, {self.user_lon}")
                return True
            else:
                print(f"❌ Invalid ZIP code: {self.zipcode}")
                return False
        except Exception as e:
            print(f"❌ Error getting location: {e}")
            return False

    def haversine_distance(self, lat1, lon1, lat2, lon2):
        """Calculate distance between two points on Earth (in km)"""
        # Convert decimal degrees to radians
        lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])

        # Haversine formula
        dlat = lat2 - lat1
        dlon = lon2 - lon1
        a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
        c = 2 * asin(sqrt(a))

        # Radius of Earth in kilometers
        r = 6371
        return c * r

    def get_aircraft_position(self):
        """Get aircraft position from OpenSky Network (free API)"""
        try:
            url = f"https://opensky-network.org/api/states/all?icao24={self.icao_code}"
            response = requests.get(url, timeout=15)

            if response.status_code == 200:
                data = response.json()
                if data['states'] and len(data['states']) > 0:
                    state = data['states'][0]
                    # OpenSky state vector format:
                    # [0]=icao24, [1]=callsign, [5]=longitude, [6]=latitude, 
                    # [7]=baro_altitude, [9]=velocity, [10]=true_track
                    return {
                        'icao24': state[0],
                        'callsign': state[1].strip() if state[1] else 'N/A',
                        'latitude': state[6],
                        'longitude': state[5],
                        'altitude_m': state[7],
                        'velocity_ms': state[9],
                        'track': state[10],
                        'on_ground': state[8]
                    }
            return None
        except Exception as e:
            print(f"⚠️  Error fetching aircraft data: {e}")
            return None

    def send_alert(self, distance_km, aircraft_info):
        """Send proximity alert"""
        current_time = time.time()

        # Check cooldown to avoid spam
        if current_time - self.last_alert_time < ALERT_COOLDOWN_SECONDS:
            return

        altitude_ft = int(aircraft_info['altitude_m'] * 3.28084) if aircraft_info['altitude_m'] else 0
        velocity_knots = int(aircraft_info['velocity_ms'] * 1.94384) if aircraft_info['velocity_ms'] else 0

        print("\n" + "="*60)
        print("🚨 PROXIMITY ALERT! 🚨")
        print("="*60)
        print(f"Aircraft: {aircraft_info['callsign']} ({aircraft_info['icao24'].upper()})")
        print(f"Distance: {distance_km:.1f} km ({distance_km * 0.621371:.1f} miles)")
        print(f"Altitude: {altitude_ft:,} ft")
        print(f"Speed: {velocity_knots} knots")
        print(f"Heading: {aircraft_info['track']}°")
        print(f"On Ground: {'Yes' if aircraft_info['on_ground'] else 'No'}")
        print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"\nMap: https://globe.adsbexchange.com/?icao={aircraft_info['icao24']}")
        print("="*60 + "\n")

        self.last_alert_time = current_time

    def monitor(self):
        """Main monitoring loop"""
        print(f"\n🛩️  Proximity Aircraft Alert System")
        print(f"Target Aircraft: {self.icao_code.upper()}")
        print(f"Alert Radius: {self.radius_km} km ({self.radius_km * 0.621371:.1f} miles)")
        print(f"Check Interval: {CHECK_INTERVAL_SECONDS} seconds\n")

        if not self.get_coordinates_from_zip():
            return

        print(f"\n✅ Monitoring started at {datetime.now().strftime('%H:%M:%S')}")
        print("Press Ctrl+C to stop\n")

        try:
            while True:
                aircraft = self.get_aircraft_position()

                if aircraft and aircraft['latitude'] and aircraft['longitude']:
                    distance = self.haversine_distance(
                        self.user_lat, self.user_lon,
                        aircraft['latitude'], aircraft['longitude']
                    )

                    # Check if within range
                    if distance <= self.radius_km:
                        if not self.currently_in_range:
                            self.currently_in_range = True
                            self.send_alert(distance, aircraft)
                        else:
                            # Already in range, just update
                            print(f"✈️  Still nearby: {distance:.1f} km | Alt: {int(aircraft['altitude_m'] * 3.28084):,} ft | {datetime.now().strftime('%H:%M:%S')}")
                    else:
                        if self.currently_in_range:
                            print(f"📤 Aircraft left proximity zone (now {distance:.1f} km away)")
                            self.currently_in_range = False
                        else:
                            print(f"🔍 Tracking... Distance: {distance:.1f} km | {datetime.now().strftime('%H:%M:%S')}")
                else:
                    if aircraft is None:
                        print(f"❌ Aircraft not found or not transmitting | {datetime.now().strftime('%H:%M:%S')}")
                    else:
                        print(f"⚠️  No position data available | {datetime.now().strftime('%H:%M:%S')}")
                    self.currently_in_range = False

                time.sleep(CHECK_INTERVAL_SECONDS)

        except KeyboardInterrupt:
            print("\n\n🛑 Monitoring stopped by user")
        except Exception as e:
            print(f"\n❌ Error: {e}")

def main():
    if len(sys.argv) != 2:
        print("Usage: python proximity_alert.py <zipcode>")
        print("Example: python proximity_alert.py 59601")
        sys.exit(1)

    zipcode = sys.argv[1]

    # Validate zip code
    if not zipcode.isdigit() or len(zipcode) != 5:
        print("❌ Please provide a valid 5-digit US ZIP code")
        sys.exit(1)

    alert = ProximityAlert(zipcode, ICAO_CODE, PROXIMITY_RADIUS_KM)
    alert.monitor()

if __name__ == "__main__":
    main()
