#!/usr/bin/env python3
"""
Unified weather station poller for all counties.
Runs continuously, polling every 15 minutes.
Generates HTML maps and JSON data files.
"""

import requests
import json
import time
import logging
import signal
import sys
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

# Configuration
POLL_INTERVAL = 900  # 15 minutes in seconds
MAX_WORKERS = 5
OUTPUT_DIR = Path(__file__).parent

# Home station - will be highlighted with a special marker
HOME_STATION_NAME = "EATON-YARA WORLD HEADQUARTERS"

# API endpoints
ALL_STATIONS_URL = "https://www.weatherlink.com/map/data"
STATION_DETAIL_URL = "https://www.weatherlink.com/map/data/station/{id}"

HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}

# All county definitions
COUNTIES = {
    'addison': {
        'name': 'Addison County, VT',
        'short_name': 'Addison County',
        'state': 'Vermont',
        'bounds': {
            'min_lat': 43.85,
            'max_lat': 44.25,
            'min_lng': -73.4,
            'max_lng': -72.9
        },
        'center': [44.05, -73.15],
        'zoom': 10,
        'btn_color': '#4a90d9',
        'group': 'addison_monterey'
    },
    'monterey': {
        'name': 'Monterey County, CA',
        'short_name': 'Monterey County',
        'state': 'California',
        'bounds': {
            'min_lat': 35.78,
            'max_lat': 36.92,
            'min_lng': -121.98,
            'max_lng': -120.21
        },
        'center': [36.35, -121.1],
        'zoom': 9,
        'btn_color': '#d97a4a',
        'group': 'addison_monterey'
    },
    'santa_clara': {
        'name': 'Santa Clara County, CA',
        'short_name': 'Santa Clara County',
        'state': 'California',
        'bounds': {
            'min_lat': 36.89,
            'max_lat': 37.48,
            'min_lng': -122.2,
            'max_lng': -121.21
        },
        'center': [37.23, -121.7],
        'zoom': 10,
        'btn_color': '#5cb85c',
        'group': 'santa_clara_middlesex'
    },
    'middlesex': {
        'name': 'Middlesex County, MA',
        'short_name': 'Middlesex County',
        'state': 'Massachusetts',
        'bounds': {
            'min_lat': 42.24,
            'max_lat': 42.73,
            'min_lng': -71.66,
            'max_lng': -71.02
        },
        'center': [42.48, -71.34],
        'zoom': 10,
        'btn_color': '#9b59b6',
        'group': 'santa_clara_middlesex'
    }
}

# Map groupings for combined HTML files
MAP_GROUPS = {
    'addison_monterey': {
        'title': 'Addison & Monterey Counties',
        'counties': ['addison', 'monterey'],
        'output_file': 'weather_map.html',
        'data_file': 'all_stations_data.json'
    },
    'santa_clara_middlesex': {
        'title': 'Santa Clara & Middlesex Counties',
        'counties': ['santa_clara', 'middlesex'],
        'output_file': 'weather_map_sc_mx.html',
        'data_file': 'sc_mx_stations_data.json'
    }
}

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

# Graceful shutdown
shutdown_requested = False

def signal_handler(signum, frame):
    global shutdown_requested
    logger.info("Shutdown requested, finishing current poll cycle...")
    shutdown_requested = True

signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)


def is_in_bounds(lat, lng, bounds):
    return (bounds['min_lat'] <= lat <= bounds['max_lat'] and
            bounds['min_lng'] <= lng <= bounds['max_lng'])


def get_temp_color(temp):
    try:
        t = float(temp)
        if t < 0: return '#29333b'
        elif t < 10: return '#535f79'
        elif t < 20: return '#2f6695'
        elif t < 32: return '#1b7dbf'
        elif t < 40: return '#27a5af'
        elif t < 50: return '#00b58d'
        elif t < 60: return '#009247'
        elif t < 70: return '#fdd03b'
        elif t < 80: return '#ed9922'
        else: return '#dd5626'
    except:
        return '#888888'


def fetch_station_detail(station):
    station_id = station.get('id')
    if not station_id:
        return None

    try:
        url = STATION_DETAIL_URL.format(id=station_id)
        resp = requests.get(url, headers=HEADERS, timeout=10)
        resp.raise_for_status()
        detail = resp.json()

        temp = detail.get('temperature', '--')
        try:
            t = float(temp)
            if t < -40 or t > 130:
                temp = '--'
        except:
            pass

        return {
            'lat': station.get('lat'),
            'lng': station.get('lng'),
            'name': detail.get('sStation', 'Unknown'),
            'temp': temp,
            'humidity': detail.get('humidity', '--'),
            'wind': detail.get('windSpeed', '--'),
            'wind_dir': detail.get('windDirection', '--'),
            'barometer': detail.get('barometer', '--'),
            'updated': detail.get('lastUpdatedAt', '--')
        }
    except Exception as e:
        return None


def poll_county(county_key, all_stations):
    county = COUNTIES[county_key]
    bounds = county['bounds']

    county_stations = [s for s in all_stations
                       if is_in_bounds(s.get('lat', 0), s.get('lng', 0), bounds)]

    if not county_stations:
        logger.warning(f"{county['name']}: No stations found in bounds")
        return [], 0, 0

    stations = []
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
        futures = {executor.submit(fetch_station_detail, s): s for s in county_stations}
        for future in as_completed(futures):
            result = future.result()
            if result:
                stations.append(result)

    temps = []
    for s in stations:
        try:
            temps.append(float(s['temp']))
        except:
            pass

    min_temp = min(temps) if temps else 0
    max_temp = max(temps) if temps else 0

    for s in stations:
        s['color'] = get_temp_color(s['temp'])
        s['county'] = county_key
        s['is_home'] = (s.get('name', '').upper() == HOME_STATION_NAME.upper())

    logger.info(f"{county['name']}: {len(stations)}/{len(county_stations)} stations, {min_temp:.0f}-{max_temp:.0f}°F")
    return stations, min_temp, max_temp


def generate_single_county_html(county_key, stations, min_temp, max_temp):
    county = COUNTIES[county_key]
    now = datetime.now()

    html = f"""<!DOCTYPE html>
<html>
<head>
    <title>{county['short_name']} Weather</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <style>
        html, body {{ margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; font-family: system-ui, -apple-system, sans-serif; font-size: 16px; }}
        #map {{ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; height: 100dvh; }}
        .info {{
            padding: 14px 18px;
            background: #fff;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,.25);
            font-size: 16px;
            line-height: 1.5;
        }}
        .info h3 {{ margin: 0 0 10px 0; font-size: 20px; }}
        .legend-row {{
            overflow: hidden;
            margin-bottom: 6px;
        }}
        .legend i {{
            display: inline-block;
            width: 24px;
            height: 24px;
            float: left;
            margin-right: 10px;
            border-radius: 4px;
        }}
        .legend span {{
            display: block;
            overflow: hidden;
            line-height: 24px;
        }}
        .leaflet-popup-content {{
            font-size: 16px;
            line-height: 1.6;
            margin: 12px 16px;
        }}
    </style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView({county['center']}, {county['zoom']});

L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
    attribution: '© OpenStreetMap | Davis Instruments'
}}).addTo(map);

var stations = {json.dumps(stations)};

// Home icon for owner's station
var homeIcon = L.divIcon({{
    className: 'home-marker',
    html: '<div style="background:linear-gradient(135deg,#ff6b6b,#ee5a5a);width:28px;height:28px;border-radius:50%;border:3px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;font-size:14px;">🏠</div>',
    iconSize: [28, 28],
    iconAnchor: [14, 14],
    popupAnchor: [0, -14]
}});

stations.forEach(function(s) {{
    var marker;
    if (s.is_home) {{
        marker = L.marker([s.lat, s.lng], {{ icon: homeIcon }});
    }} else {{
        marker = L.circleMarker([s.lat, s.lng], {{
            radius: 10,
            fillColor: s.color,
            color: '#fff',
            weight: 2,
            fillOpacity: 0.9
        }});
    }}
    marker.bindPopup(
        '<b>' + s.name + '</b>' + (s.is_home ? ' 🏠' : '') + '<br>' +
        '🌡️ <b>' + s.temp + '°F</b><br>' +
        '💧 ' + s.humidity + '% humidity<br>' +
        '💨 ' + s.wind + ' mph ' + s.wind_dir + '<br>' +
        '📊 ' + s.barometer + ' in Hg<br>' +
        '<small>' + s.updated + '</small>'
    ).addTo(map);
    marker.on('mouseover', function() {{ this.openPopup(); }});
    marker.on('mouseout', function() {{ this.closePopup(); }});
}});

var info = L.control({{ position: 'topright' }});
info.onAdd = function() {{
    var div = L.DomUtil.create('div', 'info');
    div.innerHTML = '<h3>🌡️ {county["name"]}</h3>' +
        '<b>{len(stations)}</b> stations<br>' +
        '<small>Range: {min_temp:.0f}°F to {max_temp:.0f}°F</small><br>' +
        '<small>Updated: {now.strftime("%I:%M %p")}</small>';
    return div;
}};
info.addTo(map);

var legend = L.control({{ position: 'bottomright' }});
legend.onAdd = function() {{
    var div = L.DomUtil.create('div', 'info legend');
    div.innerHTML = '<b>Temperature</b>' +
        '<div class="legend-row"><i style="background:#2f6695"></i><span>&lt;20°F</span></div>' +
        '<div class="legend-row"><i style="background:#1b7dbf"></i><span>20-31°F</span></div>' +
        '<div class="legend-row"><i style="background:#27a5af"></i><span>32-39°F</span></div>' +
        '<div class="legend-row"><i style="background:#00b58d"></i><span>40-49°F</span></div>' +
        '<div class="legend-row"><i style="background:#009247"></i><span>50-59°F</span></div>' +
        '<div class="legend-row"><i style="background:#fdd03b"></i><span>60-69°F</span></div>' +
        '<div class="legend-row"><i style="background:#ed9922"></i><span>70°F+</span></div>';
    return div;
}};
legend.addTo(map);
</script>
</body>
</html>"""

    return html


def generate_combined_html(group_key, county_data):
    group = MAP_GROUPS[group_key]
    now = datetime.now()

    all_stations = []
    county_stats = {}
    for county_key in group['counties']:
        if county_key in county_data:
            stations, min_temp, max_temp = county_data[county_key]
            all_stations.extend(stations)
            county_stats[county_key] = {
                'count': len(stations),
                'min': min_temp,
                'max': max_temp
            }

    county1, county2 = group['counties']
    c1, c2 = COUNTIES[county1], COUNTIES[county2]

    html = f"""<!DOCTYPE html>
<html>
<head>
    <title>Weather Map - {group['title']}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <style>
        html, body {{ margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; font-family: system-ui, -apple-system, sans-serif; font-size: 16px; }}
        #map {{ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; height: 100dvh; }}
        .info {{
            padding: 14px 18px;
            background: #fff;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,.25);
            font-size: 16px;
            line-height: 1.5;
        }}
        .info h3 {{ margin: 0 0 10px 0; font-size: 20px; }}
        .legend {{
            font-size: 15px;
        }}
        .legend-row {{
            overflow: hidden;
            margin-bottom: 6px;
        }}
        .legend i {{
            display: inline-block;
            width: 24px;
            height: 24px;
            float: left;
            margin-right: 10px;
            border-radius: 4px;
        }}
        .legend span {{
            display: block;
            overflow: hidden;
            line-height: 24px;
        }}
        .county-btn {{
            display: block;
            width: 100%;
            padding: 12px 16px;
            margin: 6px 0;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            text-align: left;
        }}
        .county-btn small {{
            font-size: 14px;
        }}
        .county-btn:hover {{ opacity: 0.8; }}
        .btn-{county1} {{ background: {c1['btn_color']}; color: white; }}
        .btn-{county2} {{ background: {c2['btn_color']}; color: white; }}
        .leaflet-popup-content {{
            font-size: 16px;
            line-height: 1.6;
            margin: 12px 16px;
        }}
        .leaflet-popup-content b {{
            font-size: 17px;
        }}
        .leaflet-popup-content small {{
            font-size: 14px;
        }}
        .leaflet-control-layers {{
            font-size: 15px;
            padding: 8px 12px;
        }}
        .leaflet-control-layers-list {{
            line-height: 2;
        }}
    </style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([39.5, -98.5], 4);

L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
    attribution: '© OpenStreetMap | Davis Instruments'
}}).addTo(map);

var stations = {json.dumps(all_stations)};

var layer1 = L.layerGroup();
var layer2 = L.layerGroup();

// Home icon for owner's station
var homeIcon = L.divIcon({{
    className: 'home-marker',
    html: '<div style="background:linear-gradient(135deg,#ff6b6b,#ee5a5a);width:28px;height:28px;border-radius:50%;border:3px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;font-size:14px;">🏠</div>',
    iconSize: [28, 28],
    iconAnchor: [14, 14],
    popupAnchor: [0, -14]
}});

stations.forEach(function(s) {{
    var marker;
    if (s.is_home) {{
        marker = L.marker([s.lat, s.lng], {{ icon: homeIcon }});
    }} else {{
        marker = L.circleMarker([s.lat, s.lng], {{
            radius: 10,
            fillColor: s.color,
            color: '#fff',
            weight: 2,
            fillOpacity: 0.9
        }});
    }}
    marker.bindPopup(
        '<b>' + s.name + '</b>' + (s.is_home ? ' 🏠' : '') + '<br>' +
        '🌡️ <b>' + s.temp + '°F</b><br>' +
        '💧 ' + s.humidity + '% humidity<br>' +
        '💨 ' + s.wind + ' mph ' + s.wind_dir + '<br>' +
        '📊 ' + s.barometer + ' in Hg<br>' +
        '<small>' + s.updated + '</small>'
    );
    marker.on('mouseover', function() {{ this.openPopup(); }});
    marker.on('mouseout', function() {{ this.closePopup(); }});

    if (s.county === '{county1}') {{
        layer1.addLayer(marker);
    }} else {{
        layer2.addLayer(marker);
    }}
}});

layer1.addTo(map);
layer2.addTo(map);

var overlays = {{
    "{c1['name']} ({county_stats.get(county1, {}).get('count', 0)})": layer1,
    "{c2['name']} ({county_stats.get(county2, {}).get('count', 0)})": layer2
}};
L.control.layers(null, overlays, {{ collapsed: false }}).addTo(map);

var info = L.control({{ position: 'topright' }});
info.onAdd = function() {{
    var div = L.DomUtil.create('div', 'info');
    div.innerHTML = '<h3>🌡️ Weather Stations</h3>' +
        '<b>{len(all_stations)}</b> total stations<br><br>' +
        '<button class="county-btn btn-{county1}" onclick="map.setView({c1['center']}, {c1['zoom']})">{c1['name']}<br><small>{county_stats.get(county1, {}).get('count', 0)} stations | {county_stats.get(county1, {}).get('min', 0):.0f}°F - {county_stats.get(county1, {}).get('max', 0):.0f}°F</small></button>' +
        '<button class="county-btn btn-{county2}" onclick="map.setView({c2['center']}, {c2['zoom']})">{c2['name']}<br><small>{county_stats.get(county2, {}).get('count', 0)} stations | {county_stats.get(county2, {}).get('min', 0):.0f}°F - {county_stats.get(county2, {}).get('max', 0):.0f}°F</small></button>' +
        '<br><small>Updated: {now.strftime("%I:%M %p")}</small>';
    return div;
}};
info.addTo(map);

var legend = L.control({{ position: 'bottomright' }});
legend.onAdd = function() {{
    var div = L.DomUtil.create('div', 'info legend');
    div.innerHTML = '<b>Temperature</b>' +
        '<div class="legend-row"><i style="background:#2f6695"></i><span>&lt;20°F</span></div>' +
        '<div class="legend-row"><i style="background:#1b7dbf"></i><span>20-31°F</span></div>' +
        '<div class="legend-row"><i style="background:#27a5af"></i><span>32-39°F</span></div>' +
        '<div class="legend-row"><i style="background:#00b58d"></i><span>40-49°F</span></div>' +
        '<div class="legend-row"><i style="background:#009247"></i><span>50-59°F</span></div>' +
        '<div class="legend-row"><i style="background:#fdd03b"></i><span>60-69°F</span></div>' +
        '<div class="legend-row"><i style="background:#ed9922"></i><span>70°F+</span></div>';
    return div;
}};
legend.addTo(map);
</script>
</body>
</html>"""

    return html


def poll_all_counties():
    logger.info("Starting poll cycle...")
    start_time = time.time()

    try:
        resp = requests.get(ALL_STATIONS_URL, headers=HEADERS, timeout=30)
        resp.raise_for_status()
        all_stations = resp.json()
        logger.info(f"Fetched {len(all_stations):,} stations worldwide")
    except Exception as e:
        logger.error(f"Failed to fetch station list: {e}")
        return False

    county_data = {}
    for county_key in COUNTIES:
        try:
            stations, min_temp, max_temp = poll_county(county_key, all_stations)
            county_data[county_key] = (stations, min_temp, max_temp)

            if stations:
                county = COUNTIES[county_key]
                html = generate_single_county_html(county_key, stations, min_temp, max_temp)
                html_file = OUTPUT_DIR / f"{county_key}_county_weather.html"
                with open(html_file, 'w') as f:
                    f.write(html)

                data_file = OUTPUT_DIR / f"{county_key}_county_data.json"
                with open(data_file, 'w') as f:
                    json.dump(stations, f, indent=2)

        except Exception as e:
            logger.error(f"Error polling {county_key}: {e}")

    for group_key, group in MAP_GROUPS.items():
        try:
            group_county_data = {k: v for k, v in county_data.items() if k in group['counties']}
            if group_county_data:
                html = generate_combined_html(group_key, group_county_data)
                html_file = OUTPUT_DIR / group['output_file']
                with open(html_file, 'w') as f:
                    f.write(html)

                all_stations_combined = []
                for county_key in group['counties']:
                    if county_key in county_data:
                        all_stations_combined.extend(county_data[county_key][0])

                data_file = OUTPUT_DIR / group['data_file']
                with open(data_file, 'w') as f:
                    json.dump(all_stations_combined, f, indent=2)

                logger.info(f"Generated {group['output_file']}")

        except Exception as e:
            logger.error(f"Error generating combined map {group_key}: {e}")

    elapsed = time.time() - start_time
    logger.info(f"Poll cycle completed in {elapsed:.1f}s")
    return True


def main():
    logger.info("=" * 60)
    logger.info("Weather Poller Service Started")
    logger.info(f"Output directory: {OUTPUT_DIR}")
    logger.info(f"Poll interval: {POLL_INTERVAL}s ({POLL_INTERVAL // 60} minutes)")
    logger.info("=" * 60)

    poll_all_counties()

    while not shutdown_requested:
        logger.info(f"Sleeping {POLL_INTERVAL}s until next poll...")

        sleep_end = time.time() + POLL_INTERVAL
        while time.time() < sleep_end and not shutdown_requested:
            time.sleep(1)

        if not shutdown_requested:
            poll_all_counties()

    logger.info("Weather Poller Service stopped")


if __name__ == "__main__":
    main()
