import io
import logging
import math

import requests
from PIL import Image

from planemapper.constants import BACKGROUND_PATH, COLOUR_WHITE, DISPLAY_HEIGHT, DISPLAY_WIDTH
from planemapper.provisioning import ProvisioningError

log = logging.getLogger(__name__)

_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
_USER_AGENT = "planemapper/0.1 (https://github.com/football2801/planeMapper)"


def lat_lon_to_tile(lat: float, lon: float, zoom: int) -> tuple[int, int]:
    x = int((lon + 180.0) / 360.0 * (1 << zoom))
    lat_r = math.radians(lat)
    y = int((1.0 - math.log(math.tan(lat_r) + 1.0 / math.cos(lat_r)) / math.pi) / 2.0 * (1 << zoom))
    return x, y


def _zoom_for_radius(radius_nm: float) -> int:
    if radius_nm > 200:
        return 8
    if radius_nm > 100:
        return 9
    if radius_nm > 50:
        return 10
    return 11


def download_and_composite(lat: float, lon: float, radius_nm: float) -> None:
    zoom = _zoom_for_radius(radius_nm)
    radius_deg = radius_nm / 60.0

    # Tile bounds
    x_min, y_max = lat_lon_to_tile(lat - radius_deg, lon - radius_deg, zoom)
    x_max, y_min = lat_lon_to_tile(lat + radius_deg, lon + radius_deg, zoom)

    canvas_w = (x_max - x_min + 1) * 256
    canvas_h = (y_max - y_min + 1) * 256

    # Guard: ensure canvas is at least display size
    canvas_w = max(canvas_w, DISPLAY_WIDTH)
    canvas_h = max(canvas_h, DISPLAY_HEIGHT)

    canvas = Image.new("RGB", (canvas_w, canvas_h), COLOUR_WHITE)

    tile_count = 0
    for tx in range(x_min, x_max + 1):
        for ty in range(y_min, y_max + 1):
            url = _TILE_URL.format(z=zoom, x=tx, y=ty)
            resp = requests.get(url, headers={"User-Agent": _USER_AGENT}, timeout=10)
            resp.raise_for_status()
            tile_img = Image.open(io.BytesIO(resp.content)).convert("RGB")
            px = (tx - x_min) * 256
            py = (ty - y_min) * 256
            canvas.paste(tile_img, (px, py))
            tile_count += 1

    # Crop to display size centred on home location
    home_tx, home_ty = lat_lon_to_tile(lat, lon, zoom)
    home_px = (home_tx - x_min) * 256 + 128
    home_py = (home_ty - y_min) * 256 + 128
    left = max(0, home_px - DISPLAY_WIDTH // 2)
    top = max(0, home_py - DISPLAY_HEIGHT // 2)
    left = min(left, canvas_w - DISPLAY_WIDTH)
    top = min(top, canvas_h - DISPLAY_HEIGHT)
    left = max(0, left)
    top = max(0, top)
    cropped = canvas.crop((left, top, left + DISPLAY_WIDTH, top + DISPLAY_HEIGHT))

    BACKGROUND_PATH.parent.mkdir(parents=True, exist_ok=True)
    cropped.save(str(BACKGROUND_PATH))
    log.info("composited %d tiles → %s", tile_count, BACKGROUND_PATH)


def validate_cache() -> None:
    if not BACKGROUND_PATH.exists():
        raise ProvisioningError("background.png not found")
    size = BACKGROUND_PATH.stat().st_size
    if size == 0:
        raise ProvisioningError("background.png is empty")
    try:
        with Image.open(BACKGROUND_PATH) as img:
            img.verify()
    except Exception as e:
        raise ProvisioningError(f"background.png is not a valid PNG: {e}") from e
    if size >= 2 * 1024**3:
        raise ProvisioningError("background.png exceeds 2GB limit")
    log.info("cache validation passed: background.png %.1f KB", size / 1024)
