"""
Cover Override API endpoints.

Handles forecast snapshots, cover overrides, and spend rate overrides
for the Spend Budget feature.
"""
from datetime import date, timedelta
from decimal import Decimal
from typing import Optional
import logging

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text, select, delete
from pydantic import BaseModel

from database import get_db
from models.user import User
from models.settings import KitchenSettings
from auth import get_current_user, require_cap
from services.forecast_api import ForecastAPIClient, ForecastAPIError

logger = logging.getLogger(__name__)

router = APIRouter()


# --- Request/Response Models ---

class SnapshotRequest(BaseModel):
    week_offset: int = 0


class CoverOverrideRequest(BaseModel):
    override_date: str  # YYYY-MM-DD
    period: str  # 'lunch' or 'dinner'
    override_covers: int  # target total covers


class SpendRateOverrideRequest(BaseModel):
    week_offset: int = 0
    period: str  # 'breakfast', 'lunch', or 'dinner'
    food_spend: Optional[float] = None
    drinks_spend: Optional[float] = None


class SnapshotData(BaseModel):
    date: str
    period: str
    forecast_covers: int
    otb_covers: int
    food_spend: Optional[float]
    drinks_spend: Optional[float]
    forecast_dry_revenue: Optional[float]


class OverrideData(BaseModel):
    id: int
    override_date: str
    period: str
    override_covers: int
    original_forecast: Optional[int]
    original_otb: Optional[int]


class SpendRateData(BaseModel):
    period: str
    food_spend_api: Optional[float]
    drinks_spend_api: Optional[float]
    food_spend_snapshot: Optional[float]
    drinks_spend_snapshot: Optional[float]
    food_spend_override: Optional[float]
    drinks_spend_override: Optional[float]
    food_spend_effective: float
    drinks_spend_effective: float


class RecalcDay(BaseModel):
    date: str
    day_name: str
    is_past: bool
    periods: dict  # period -> {actual, otb, pickup, effective, override, snapshot, variance}
    day_revenue: float


class WeeklyOverrideResponse(BaseModel):
    week_start: str
    week_end: str
    has_snapshot: bool
    vat_rate: float = 1.20
    snapshot_revenue: Optional[float] = None
    adjusted_revenue: Optional[float] = None
    snapshots: list[SnapshotData] = []
    overrides: list[OverrideData] = []
    spend_rates: list[SpendRateData] = []
    recalc_days: list[RecalcDay] = []


# --- Helpers ---

def get_week_dates(week_offset: int = 0) -> tuple[date, date, list[date]]:
    today = date.today()
    current_monday = today - timedelta(days=today.weekday())
    week_start = current_monday + timedelta(weeks=week_offset)
    week_end = week_start + timedelta(days=6)
    week_dates = [week_start + timedelta(days=i) for i in range(7)]
    return week_start, week_end, week_dates


async def get_settings(db: AsyncSession, kitchen_id: int) -> KitchenSettings:
    result = await db.execute(
        select(KitchenSettings).where(KitchenSettings.kitchen_id == kitchen_id)
    )
    settings = result.scalar_one_or_none()
    if not settings:
        raise HTTPException(status_code=404, detail="Kitchen settings not found")
    return settings


# --- Endpoints ---

@router.get("/weekly", response_model=WeeklyOverrideResponse)
async def get_weekly_overrides(
    week_offset: int = 0,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Get snapshot, overrides, spend rates, and recalculated breakdown for a week."""
    week_start, week_end, week_dates = get_week_dates(week_offset)
    kitchen_id = current_user.kitchen_id
    today = date.today()

    # Check if snapshot exists
    snap_result = await db.execute(text("""
        SELECT snapshot_date, period, forecast_covers, otb_covers,
               food_spend, drinks_spend, forecast_dry_revenue
        FROM forecast_snapshots
        WHERE kitchen_id = :kid AND week_start = :ws
        ORDER BY snapshot_date, period
    """), {"kid": kitchen_id, "ws": week_start})
    snap_rows = snap_result.fetchall()

    has_snapshot = len(snap_rows) > 0
    snapshots = []
    snap_lookup = {}  # (date_str, period) -> row

    for row in snap_rows:
        date_str = row.snapshot_date.isoformat() if hasattr(row.snapshot_date, 'isoformat') else str(row.snapshot_date)
        snapshots.append(SnapshotData(
            date=date_str,
            period=row.period,
            forecast_covers=row.forecast_covers,
            otb_covers=row.otb_covers,
            food_spend=float(row.food_spend) if row.food_spend else None,
            drinks_spend=float(row.drinks_spend) if row.drinks_spend else None,
            forecast_dry_revenue=float(row.forecast_dry_revenue) if row.forecast_dry_revenue else None,
        ))
        snap_lookup[(date_str, row.period)] = row

    # Get week snapshot for revenue totals
    week_snap_result = await db.execute(text("""
        SELECT total_forecast_revenue, total_otb_revenue, gp_target
        FROM forecast_week_snapshots
        WHERE kitchen_id = :kid AND week_start = :ws
    """), {"kid": kitchen_id, "ws": week_start})
    week_snap = week_snap_result.fetchone()
    snapshot_revenue = float(week_snap.total_forecast_revenue) if week_snap and week_snap.total_forecast_revenue else None

    # Get overrides
    override_result = await db.execute(text("""
        SELECT id, override_date, period, override_covers, original_forecast, original_otb
        FROM cover_overrides
        WHERE kitchen_id = :kid
          AND override_date >= :ws AND override_date <= :we
        ORDER BY override_date, period
    """), {"kid": kitchen_id, "ws": week_start, "we": week_end})
    override_rows = override_result.fetchall()

    overrides = []
    override_lookup = {}  # (date_str, period) -> row
    for row in override_rows:
        date_str = row.override_date.isoformat() if hasattr(row.override_date, 'isoformat') else str(row.override_date)
        overrides.append(OverrideData(
            id=row.id,
            override_date=date_str,
            period=row.period,
            override_covers=row.override_covers,
            original_forecast=row.original_forecast,
            original_otb=row.original_otb,
        ))
        override_lookup[(date_str, row.period)] = row

    # Get spend rate overrides
    spend_override_result = await db.execute(text("""
        SELECT id, period, food_spend, drinks_spend
        FROM spend_rate_overrides
        WHERE kitchen_id = :kid AND week_start = :ws
    """), {"kid": kitchen_id, "ws": week_start})
    spend_override_rows = spend_override_result.fetchall()
    spend_override_lookup = {row.period: row for row in spend_override_rows}

    # Fetch live forecast data + spend rates from API
    settings = await get_settings(db, kitchen_id)
    api_spend_rates = {}
    api_vat_rate = 1.20
    covers_data = []
    revenue_data = []

    if settings.forecast_api_url and settings.forecast_api_key:
        try:
            async with ForecastAPIClient(settings.forecast_api_url, settings.forecast_api_key) as client:
                try:
                    sr = await client.get_spend_rates()
                    api_spend_rates = sr.get("periods", {})
                    api_vat_rate = sr.get("vat_rate", 1.20)
                except Exception as e:
                    logger.warning(f"Failed to fetch spend rates: {e}")

                try:
                    covers_data = await client.get_covers_forecast(week_start, days=7)
                except Exception as e:
                    logger.warning(f"Failed to fetch covers: {e}")

                try:
                    revenue_data = await client.get_revenue_forecast(week_start, days=7)
                except Exception as e:
                    logger.warning(f"Failed to fetch revenue: {e}")
        except Exception as e:
            logger.warning(f"Failed to connect to forecast API: {e}")

    # Build spend rates response (per period)
    spend_rates = []
    spend_effective = {}  # period -> {food, drinks}
    for period in ("breakfast", "lunch", "dinner"):
        api_food = api_spend_rates.get(period, {}).get("food_spend_net", 0)
        api_drinks = api_spend_rates.get(period, {}).get("drinks_spend_net", 0)

        # Get snapshot values (from first day of snapshot, same for all days in a period)
        snap_food = None
        snap_drinks = None
        for d in week_dates:
            key = (d.isoformat(), period)
            if key in snap_lookup:
                snap_food = float(snap_lookup[key].food_spend) if snap_lookup[key].food_spend else None
                snap_drinks = float(snap_lookup[key].drinks_spend) if snap_lookup[key].drinks_spend else None
                break

        # Get override values
        ovr = spend_override_lookup.get(period)
        ovr_food = float(ovr.food_spend) if ovr and ovr.food_spend else None
        ovr_drinks = float(ovr.drinks_spend) if ovr and ovr.drinks_spend else None

        # Resolve effective: override > snapshot > API
        eff_food = ovr_food if ovr_food is not None else (snap_food if snap_food is not None else api_food)
        eff_drinks = ovr_drinks if ovr_drinks is not None else (snap_drinks if snap_drinks is not None else api_drinks)

        spend_effective[period] = {"food": eff_food, "drinks": eff_drinks}
        spend_rates.append(SpendRateData(
            period=period,
            food_spend_api=api_food,
            drinks_spend_api=api_drinks,
            food_spend_snapshot=snap_food,
            drinks_spend_snapshot=snap_drinks,
            food_spend_override=ovr_food,
            drinks_spend_override=ovr_drinks,
            food_spend_effective=eff_food,
            drinks_spend_effective=eff_drinks,
        ))

    # Build recalculated days
    covers_by_date = {d.get("date", ""): d for d in covers_data}
    revenue_by_date = {d.get("date", ""): d for d in revenue_data}
    recalc_days = []
    total_adjusted_revenue = Decimal("0")

    for d in week_dates:
        date_str = d.isoformat()
        is_past = d < today
        day_covers = covers_by_date.get(date_str, {})
        day_revenue = revenue_by_date.get(date_str, {})
        day_name = day_covers.get("day", d.strftime("%a"))

        periods_data = {}
        day_rev = Decimal("0")

        for period in ("breakfast", "lunch", "dinner"):
            p_covers = day_covers.get(period, {})
            otb = p_covers.get("otb", 0) or 0
            forecast = p_covers.get("forecast", 0) or 0
            pickup = forecast - otb

            # Get snapshot and override for this day/period
            snap = snap_lookup.get((date_str, period))
            ovr = override_lookup.get((date_str, period))

            snap_forecast = snap.forecast_covers if snap else None
            override_val = ovr.override_covers if ovr else None

            if is_past:
                # Past day: use actual (forecast value from API is actual for past dates)
                effective = forecast
                actual = forecast
                # Calculate variance vs override or snapshot
                variance = None
                if override_val is not None:
                    diff = actual - override_val
                    if diff != 0:
                        variance = diff
                elif snap_forecast is not None:
                    diff = actual - snap_forecast
                    if diff != 0:
                        variance = diff

                # Past revenue from API (dry revenue)
                dry = day_revenue.get("dry", {})
                period_rev = Decimal(str(dry.get("forecast", 0) or 0)) if period == "breakfast" else Decimal("0")
                # For past days, use actual total revenue from the API (proportioned by period isn't available,
                # so we'll use effective_covers * spend_rates as approximation, but actual total from revenue API)
                eff_food = Decimal(str(spend_effective.get(period, {}).get("food", 0)))
                period_rev = Decimal(str(effective)) * eff_food

                periods_data[period] = {
                    "actual": actual,
                    "otb": otb,
                    "pickup": 0,
                    "effective": effective,
                    "override": override_val,
                    "snapshot": snap_forecast,
                    "variance": variance,
                    "is_overridden": False,
                }
            else:
                # Today/future: apply override logic
                if override_val is not None:
                    if otb >= override_val:
                        effective = otb  # OTB supersedes upward
                        adj_pickup = 0
                    else:
                        effective = override_val
                        adj_pickup = override_val - otb
                    is_overridden = True
                else:
                    effective = forecast
                    adj_pickup = pickup
                    is_overridden = False

                # Calculate revenue for this period (dry/food only - budget tracks food revenue)
                eff_food = Decimal(str(spend_effective.get(period, {}).get("food", 0)))
                period_rev = Decimal(str(effective)) * eff_food

                periods_data[period] = {
                    "actual": None,
                    "otb": otb,
                    "pickup": adj_pickup,
                    "effective": effective,
                    "override": override_val,
                    "snapshot": snap_forecast,
                    "variance": None,
                    "is_overridden": is_overridden,
                }

            day_rev += period_rev

        # For past days, use actual revenue from API if available
        if is_past and day_revenue:
            dry = day_revenue.get("dry", {})
            actual_dry_rev = Decimal(str(dry.get("forecast", 0) or 0))
            if actual_dry_rev > 0:
                day_rev = actual_dry_rev

        total_adjusted_revenue += day_rev

        recalc_days.append(RecalcDay(
            date=date_str,
            day_name=day_name,
            is_past=is_past,
            periods=periods_data,
            day_revenue=float(round(day_rev, 2)),
        ))

    return WeeklyOverrideResponse(
        week_start=week_start.isoformat(),
        week_end=week_end.isoformat(),
        has_snapshot=has_snapshot,
        vat_rate=api_vat_rate,
        snapshot_revenue=snapshot_revenue,
        adjusted_revenue=float(round(total_adjusted_revenue, 2)),
        snapshots=snapshots,
        overrides=overrides,
        spend_rates=spend_rates,
        recalc_days=recalc_days,
    )


@router.post("/snapshot")
async def create_snapshot(
    req: SnapshotRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Take a snapshot of the current forecast for a week (all periods, all days)."""
    week_start, week_end, week_dates = get_week_dates(req.week_offset)
    kitchen_id = current_user.kitchen_id
    settings = await get_settings(db, kitchen_id)

    if not settings.forecast_api_url or not settings.forecast_api_key:
        raise HTTPException(status_code=400, detail="Forecast API not configured")

    async with ForecastAPIClient(settings.forecast_api_url, settings.forecast_api_key) as client:
        covers_data = await client.get_covers_forecast(week_start, days=7)
        revenue_data = await client.get_revenue_forecast(week_start, days=7)
        spend_rates_response = await client.get_spend_rates()

    api_spend = spend_rates_response.get("periods", {})
    vat_rate = spend_rates_response.get("vat_rate", 1.20)

    covers_by_date = {d.get("date", ""): d for d in covers_data}
    revenue_by_date = {d.get("date", ""): d for d in revenue_data}

    # Delete existing snapshots for this week (re-snapshot)
    await db.execute(text("""
        DELETE FROM forecast_snapshots
        WHERE kitchen_id = :kid AND week_start = :ws
    """), {"kid": kitchen_id, "ws": week_start})

    # Insert snapshot for each day/period
    total_forecast_rev = Decimal("0")
    total_otb_rev = Decimal("0")

    for d in week_dates:
        date_str = d.isoformat()
        day_covers = covers_by_date.get(date_str, {})
        day_revenue = revenue_by_date.get(date_str, {})

        for period in ("breakfast", "lunch", "dinner"):
            p = day_covers.get(period, {})
            otb = p.get("otb", 0) or 0
            forecast = p.get("forecast", 0) or 0

            # Spend rates (net, ex VAT)
            food_net = api_spend.get(period, {}).get("food_spend_net", 0)
            drinks_net = api_spend.get(period, {}).get("drinks_spend_net", 0)

            # Calculate dry revenue for this period/day
            dry_rev = Decimal(str(forecast)) * Decimal(str(food_net))

            await db.execute(text("""
                INSERT INTO forecast_snapshots
                    (kitchen_id, snapshot_date, period, forecast_covers, otb_covers,
                     food_spend, drinks_spend, forecast_dry_revenue, week_start)
                VALUES (:kid, :sd, :period, :fc, :oc, :fs, :ds, :dr, :ws)
            """), {
                "kid": kitchen_id, "sd": d, "period": period,
                "fc": forecast, "oc": otb,
                "fs": food_net, "ds": drinks_net,
                "dr": float(round(dry_rev, 2)),
                "ws": week_start,
            })

            # Accumulate weekly totals from revenue API
            dry = day_revenue.get("dry", {})
            total_forecast_rev += Decimal(str(dry.get("forecast", 0) or 0))
            total_otb_rev += Decimal(str(dry.get("otb", 0) or 0))

    # Divide by 3 since we're iterating 3 periods but revenue data is per-day total
    # Actually, the revenue API returns per-day totals, not per-period.
    # We accumulated 3x per day. Let's recalculate from revenue_data directly.
    total_forecast_rev = Decimal("0")
    total_otb_rev = Decimal("0")
    for d in revenue_data:
        dry = d.get("dry", {})
        total_forecast_rev += Decimal(str(dry.get("forecast", 0) or 0))
        total_otb_rev += Decimal(str(dry.get("otb", 0) or 0))

    # Upsert week snapshot
    gp_target = float(settings.budget_gp_target) if settings.budget_gp_target else 65.0

    await db.execute(text("""
        DELETE FROM forecast_week_snapshots
        WHERE kitchen_id = :kid AND week_start = :ws
    """), {"kid": kitchen_id, "ws": week_start})

    await db.execute(text("""
        INSERT INTO forecast_week_snapshots
            (kitchen_id, week_start, total_forecast_revenue, total_otb_revenue, gp_target)
        VALUES (:kid, :ws, :tfr, :tor, :gp)
    """), {
        "kid": kitchen_id, "ws": week_start,
        "tfr": float(round(total_forecast_rev, 2)),
        "tor": float(round(total_otb_rev, 2)),
        "gp": gp_target,
    })

    await db.commit()

    return {
        "success": True,
        "message": "Forecast snapshot created",
        "week_start": week_start.isoformat(),
        "total_forecast_revenue": float(round(total_forecast_rev, 2)),
        "total_otb_revenue": float(round(total_otb_rev, 2)),
        "days_snapshotted": len(week_dates),
        "periods_per_day": 3,
    }


@router.put("")
async def upsert_cover_override(
    req: CoverOverrideRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Set or update a cover override for a specific date+period."""
    kitchen_id = current_user.kitchen_id

    if req.period not in ("lunch", "dinner"):
        raise HTTPException(status_code=400, detail="Period must be 'lunch' or 'dinner'")

    if req.override_covers < 0:
        raise HTTPException(status_code=400, detail="Override covers cannot be negative")

    override_date = date.fromisoformat(req.override_date)

    # Check existing
    existing = await db.execute(text("""
        SELECT id FROM cover_overrides
        WHERE kitchen_id = :kid AND override_date = :od AND period = :p
    """), {"kid": kitchen_id, "od": override_date, "p": req.period})
    row = existing.fetchone()

    if row:
        await db.execute(text("""
            UPDATE cover_overrides
            SET override_covers = :oc, updated_by = :uid, updated_at = NOW()
            WHERE id = :id
        """), {"oc": req.override_covers, "uid": current_user.id, "id": row.id})
    else:
        # Get current forecast for snapshot
        settings = await get_settings(db, kitchen_id)
        original_forecast = None
        original_otb = None

        if settings.forecast_api_url and settings.forecast_api_key:
            try:
                async with ForecastAPIClient(settings.forecast_api_url, settings.forecast_api_key) as client:
                    covers = await client.get_covers_forecast(override_date, days=1)
                    if covers:
                        p = covers[0].get(req.period, {})
                        original_forecast = p.get("forecast", 0) or 0
                        original_otb = p.get("otb", 0) or 0
            except Exception as e:
                logger.warning(f"Failed to get forecast for snapshot: {e}")

        await db.execute(text("""
            INSERT INTO cover_overrides
                (kitchen_id, override_date, period, override_covers,
                 original_forecast, original_otb, created_by, updated_by)
            VALUES (:kid, :od, :p, :oc, :of, :oo, :uid, :uid)
        """), {
            "kid": kitchen_id, "od": override_date, "p": req.period,
            "oc": req.override_covers, "of": original_forecast, "oo": original_otb,
            "uid": current_user.id,
        })

    await db.commit()
    return {"success": True, "date": req.override_date, "period": req.period, "covers": req.override_covers}


@router.delete("/{override_id}")
async def delete_cover_override(
    override_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Remove a cover override (revert to forecast)."""
    result = await db.execute(text("""
        DELETE FROM cover_overrides
        WHERE id = :id AND kitchen_id = :kid
        RETURNING id
    """), {"id": override_id, "kid": current_user.kitchen_id})
    deleted = result.fetchone()

    if not deleted:
        raise HTTPException(status_code=404, detail="Override not found")

    await db.commit()
    return {"success": True, "deleted_id": override_id}


@router.put("/spend-rates")
async def upsert_spend_rate_override(
    req: SpendRateOverrideRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Set or update a spend rate override for a week+period."""
    kitchen_id = current_user.kitchen_id
    week_start, _, _ = get_week_dates(req.week_offset)

    if req.period not in ("breakfast", "lunch", "dinner"):
        raise HTTPException(status_code=400, detail="Period must be 'breakfast', 'lunch', or 'dinner'")

    # Check existing
    existing = await db.execute(text("""
        SELECT id FROM spend_rate_overrides
        WHERE kitchen_id = :kid AND week_start = :ws AND period = :p
    """), {"kid": kitchen_id, "ws": week_start, "p": req.period})
    row = existing.fetchone()

    if row:
        await db.execute(text("""
            UPDATE spend_rate_overrides
            SET food_spend = :fs, drinks_spend = :ds, updated_by = :uid, updated_at = NOW()
            WHERE id = :id
        """), {"fs": req.food_spend, "ds": req.drinks_spend, "uid": current_user.id, "id": row.id})
    else:
        await db.execute(text("""
            INSERT INTO spend_rate_overrides
                (kitchen_id, week_start, period, food_spend, drinks_spend, created_by, updated_by)
            VALUES (:kid, :ws, :p, :fs, :ds, :uid, :uid)
        """), {
            "kid": kitchen_id, "ws": week_start, "p": req.period,
            "fs": req.food_spend, "ds": req.drinks_spend, "uid": current_user.id,
        })

    await db.commit()
    return {"success": True, "week_start": week_start.isoformat(), "period": req.period}


@router.delete("/spend-rates/{override_id}")
async def delete_spend_rate_override(
    override_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Remove a spend rate override (revert to snapshot/API value)."""
    result = await db.execute(text("""
        DELETE FROM spend_rate_overrides
        WHERE id = :id AND kitchen_id = :kid
        RETURNING id
    """), {"id": override_id, "kid": current_user.kitchen_id})
    deleted = result.fetchone()

    if not deleted:
        raise HTTPException(status_code=404, detail="Spend rate override not found")

    await db.commit()
    return {"success": True, "deleted_id": override_id}
