"""
Energy consumption forecasting microservice.

Exposes POST /forecast/{horizon} for horizon in {hourly, daily, weekly, monthly},
matching App\\Services\\ForecastingClient on the Laravel side.

This is a working starting point using a simple statistical baseline
(moving-average + weekday seasonality) so the endpoint is functional out
of the box. Swap `predict_baseline` for a trained Prophet / LSTM /
XGBoost model per meter as historical data accumulates -- the DB query
in `fetch_history` and the response contract are the integration points
that matter; the model behind them can evolve independently.
"""
from datetime import date, timedelta
from typing import Optional

import numpy as np
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Energy Forecasting Service")

HORIZON_DAYS = {"hourly": 1, "daily": 1, "weekly": 7, "monthly": 30}


class ForecastRequest(BaseModel):
    meter_id: int
    meter_number: str
    tariff_category: str = "residential"


class ForecastResponse(BaseModel):
    prediction_date: str
    predicted_usage: float
    confidence_score: float
    model_used: str
    estimated_recharge_date: Optional[str] = None


def fetch_history(meter_id: int) -> list[float]:
    """
    Placeholder for a real query against the `meter_readings` table
    (via a shared DB connection or an internal Laravel API call) that
    returns recent daily kWh usage for this meter. Returns a small
    synthetic series so the service is runnable standalone/for demos.
    """
    rng = np.random.default_rng(seed=meter_id)
    return list(rng.normal(loc=8.0, scale=1.5, size=30).clip(min=0.5))


def predict_baseline(history: list[float], days: int) -> tuple[float, float]:
    if not history:
        return 0.0, 0.0

    avg = float(np.mean(history[-14:]))
    trend = float(np.mean(history[-7:]) - np.mean(history[-14:-7])) if len(history) >= 14 else 0.0
    predicted = max(0.0, (avg + trend) * days)

    # Confidence shrinks with longer horizons and higher variance.
    variance_penalty = min(0.4, float(np.std(history)) / (avg + 1e-6) * 0.2)
    confidence = max(0.4, 0.9 - (days / 30) * 0.3 - variance_penalty)

    return round(predicted, 4), round(confidence, 4)


@app.post("/forecast/{horizon}", response_model=ForecastResponse)
def forecast(horizon: str, payload: ForecastRequest):
    if horizon not in HORIZON_DAYS:
        raise HTTPException(status_code=400, detail="Invalid horizon")

    history = fetch_history(payload.meter_id)
    days = HORIZON_DAYS[horizon]
    predicted_usage, confidence = predict_baseline(history, days)

    avg_daily = predicted_usage / days if days else predicted_usage
    recharge_date = None
    if avg_daily > 0:
        # crude heuristic: assumes ~10 units of headroom; the real
        # implementation should factor in the meter's actual balance,
        # passed in from Laravel.
        recharge_date = (date.today() + timedelta(days=int(10 / avg_daily))).isoformat()

    return ForecastResponse(
        prediction_date=date.today().isoformat(),
        predicted_usage=predicted_usage,
        confidence_score=confidence,
        model_used="moving_average_baseline_v1",
        estimated_recharge_date=recharge_date,
    )


@app.get("/health")
def health():
    return {"status": "ok"}
