from datetime import datetime, timezone, timedelta
import math
import httpx
from fastapi import FastAPI, Query
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI(title="TectonicWatch", version="0.1.0")
app.mount("/static", StaticFiles(directory="static"), name="static")

USGS = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson"


def haversine_km(lat1, lon1, lat2, lon2):
    r = 6371.0
    p1, p2 = math.radians(lat1), math.radians(lat2)
    dp = math.radians(lat2-lat1)
    dl = math.radians(lon2-lon1)
    a = math.sin(dp/2)**2 + math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
    return 2*r*math.asin(math.sqrt(a))


@app.get("/api/earthquakes")
async def earthquakes(days: int = Query(30, ge=1, le=3650), minmag: float = Query(2.5, ge=0, le=10)):
    end = datetime.now(timezone.utc)
    start = end - timedelta(days=days)
    url = "https://earthquake.usgs.gov/fdsnws/event/1/query"
    params = {
        "format": "geojson",
        "starttime": start.isoformat(),
        "endtime": end.isoformat(),
        "minmagnitude": minmag,
        "orderby": "time-asc",
        "limit": 20000,
    }
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.get(url, params=params)
        r.raise_for_status()
        data = r.json()
    return data


@app.get("/api/predictions")
async def predictions(days: int = Query(30, ge=1, le=365), minmag: float = Query(4.0, ge=0, le=9)):
    """Experimental forecast ranking.

    This is deliberately labeled a research score, not a calibrated probability.
    It identifies areas with elevated recent activity. It must be back-tested
    before any score is interpreted as a real probability of occurrence.
    """
    end = datetime.now(timezone.utc)
    start = end - timedelta(days=90)
    url = "https://earthquake.usgs.gov/fdsnws/event/1/query"
    params = {"format":"geojson", "starttime":start.isoformat(), "endtime":end.isoformat(),
              "minmagnitude":minmag, "orderby":"time-asc", "limit":20000}
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.get(url, params=params)
        r.raise_for_status()
        features = r.json().get("features", [])

    # Grid-based clustering score. This is intentionally transparent and temporary.
    cells = {}
    for f in features:
        c = f.get("geometry", {}).get("coordinates", [None,None,None])
        if c[0] is None or c[1] is None: continue
        lon, lat, depth = c
        mag = f.get("properties", {}).get("mag") or 0
        t = f.get("properties", {}).get("time") or 0
        age_days = max(0.0, (end.timestamp()*1000 - t) / 86400000)
        # spatial bins ~2 degrees; temporal decay; magnitude weighting
        key = (round(lat/2)*2, round(lon/2)*2)
        weight = (10 ** (0.75*max(0, mag-4))) * math.exp(-age_days/21)
        cells.setdefault(key, {"score":0.0,"events":0,"maxmag":0.0,"depths":[],"weighted_lat":0.0,"weighted_lon":0.0})
        q = cells[key]
        q["score"] += weight
        q["events"] += 1
        q["maxmag"] = max(q["maxmag"], mag)
        q["depths"].append(depth or 0)
        q["weighted_lat"] += lat*weight
        q["weighted_lon"] += lon*weight

    ranked = sorted(cells.items(), key=lambda kv: kv[1]["score"], reverse=True)[:40]
    if not ranked:
        return {"model":"experimental-activity-score-v0.1", "predictions":[]}
    maxscore = ranked[0][1]["score"]
    out = []
    for (lat, lon), q in ranked:
        # Score is normalized only for display; not a calibrated probability.
        activity = 100 * q["score"] / maxscore if maxscore else 0
        depth = sum(q["depths"])/len(q["depths"])
        expected_mag = min(8.5, max(4.5, q["maxmag"] + 0.25))
        # Conservative display confidence: activity score, capped below certainty.
        confidence = min(85.0, 15.0 + 0.70*activity)
        out.append({
            "lat": round(q["weighted_lat"]/q["score"],3),
            "lon": round(q["weighted_lon"]/q["score"],3),
            "confidence": round(confidence,1),
            "expected_magnitude": round(expected_mag,1),
            "expected_depth_km": round(depth,1),
            "activity_score": round(activity,1),
            "events_90d": q["events"],
            "model_status":"UNVALIDATED EXPERIMENTAL SCORE"
        })
    return {"model":"experimental-activity-score-v0.1", "forecast_window_days":days,
            "predictions":out}


@app.get("/", response_class=HTMLResponse)
async def index():
    with open("static/index.html", "r", encoding="utf-8") as f:
        return f.read()
