import os
from collections import defaultdict
from pathlib import Path
import json

try:
    import mapbox_vector_tile
except ModuleNotFoundError:  # pragma: no cover - dependency guard for runtime environments
    mapbox_vector_tile = None

try:
    import mercantile
except ModuleNotFoundError:  # pragma: no cover - dependency guard for runtime environments
    mercantile = None

PROJECT_ROOT = Path(__file__).resolve().parent.parent
GRID_DIR = PROJECT_ROOT / "data" / "grid"
OUTPUT_DIR = Path("/home/wwwroot/weather")

DEFAULT_ZOOMS = [2, 4, 6, 8, 10, 12]


def get_zoom_levels():
    raw_value = os.environ.get("WEATHER_TILE_ZOOMS", "")
    if not raw_value.strip():
        return DEFAULT_ZOOMS

    zooms = []
    for chunk in raw_value.split(","):
        chunk = chunk.strip()
        if not chunk:
            continue
        zoom = int(chunk)
        if zoom < 0:
            raise ValueError(f"Invalid zoom level: {zoom}")
        zooms.append(zoom)

    if not zooms:
        raise ValueError("WEATHER_TILE_ZOOMS did not contain any usable zoom levels")

    return sorted(set(zooms))


def load_grid(path):
    with path.open(encoding="utf-8") as file_handle:
        data = json.load(file_handle)
    return data["time"], data["grid"]


def get_grid_field(grid, field_name, latitudes, longitudes):
    values = grid.get(field_name)
    if values is not None:
        return values

    return [[0.0 for _ in longitudes] for _ in latitudes]


def grid_to_features(grid):
    latitudes = grid["lat"]
    longitudes = grid["lon"]
    wind_speed = grid["wind_speed"]
    wind_dir = grid["wind_dir"]
    rain = grid["rain"]
    temp = grid["temp"]
    pressure = grid["pressure"]
    wave_h = get_grid_field(grid, "wave_h", latitudes, longitudes)
    wave_dir = get_grid_field(grid, "wave_dir", latitudes, longitudes)
    wave_period = get_grid_field(grid, "wave_period", latitudes, longitudes)

    features = []
    for lat_index, latitude in enumerate(latitudes):
        for lon_index, longitude in enumerate(longitudes):
            features.append(
                {
                    "geometry": {"type": "Point", "coordinates": [longitude, latitude]},
                    "properties": {
                        "ws": wind_speed[lat_index][lon_index],
                        "wd": wind_dir[lat_index][lon_index],
                        "r": rain[lat_index][lon_index],
                        "t": temp[lat_index][lon_index],
                        "p": pressure[lat_index][lon_index],
                        "wh": wave_h[lat_index][lon_index],
                        "wdir": wave_dir[lat_index][lon_index],
                        "wp": wave_period[lat_index][lon_index],
                    },
                }
            )
    return features


def bucket_features_by_tile(features, zoom):
    buckets = defaultdict(list)
    for feature in features:
        longitude, latitude = feature["geometry"]["coordinates"]
        tile = mercantile.tile(longitude, latitude, zoom)
        buckets[(tile.x, tile.y)].append(feature)
    return buckets


def write_tile(tile_time, zoom, tile_x, tile_y, features):
    bounds = mercantile.bounds(mercantile.Tile(x=tile_x, y=tile_y, z=zoom))
    tile_dir = OUTPUT_DIR / tile_time / str(zoom) / str(tile_x)
    tile_dir.mkdir(parents=True, exist_ok=True)
    tile_path = tile_dir / f"{tile_y}.pbf"

    layer = {"name": "weather", "features": features}
    tile_data = mapbox_vector_tile.encode(
        layer,
        default_options={
            "quantize_bounds": (bounds.west, bounds.south, bounds.east, bounds.north),
        },
    )
    tile_path.write_bytes(tile_data)
    print("tile", tile_time, zoom, tile_x, tile_y)


def generate_tiles(tile_time, features):
    for zoom in get_zoom_levels():
        buckets = bucket_features_by_tile(features, zoom)
        for (tile_x, tile_y), tile_features in buckets.items():
            write_tile(tile_time, zoom, tile_x, tile_y, tile_features)


def main():
    if mapbox_vector_tile is None or mercantile is None:
        raise RuntimeError("mapbox-vector-tile and mercantile are required to generate vector tiles")

    for path in sorted(GRID_DIR.glob("*.json")):
        print("processing", path.name)
        tile_time, grid = load_grid(path)
        features = grid_to_features(grid)
        generate_tiles(tile_time, features)


if __name__ == "__main__":
    main()
