import asyncio
import logging

from sqlalchemy import text, bindparam
from pydantic import BaseModel
from fastapi import APIRouter, Path

from datetime import datetime
from zoneinfo import ZoneInfo

from app.schemas import ResponseWrapper
from app.exceptions import HTTPException
from app.models.connection import get_async_db
from app.services.predict_price import predict_30_days_data
from app.utils.competitors_prices import adjust_predictions_with_competitors, get_competitors_prices
from app.utils.events import apply_events
from app.utils.holidays import apply_holidays

router = APIRouter()

logger = logging.getLogger(__name__)

# --- Thread-safe cache state ---
_cache = {}
_cache_date = None

_competitors_cache = None
_competitors_cache_date = None

_rooms_cache = {}
_rooms_cache_date = None

_cache_lock = asyncio.Lock()

class PricingRequest(BaseModel):
    prices: dict
    rooms: dict
    competitors: dict

async def fetch_rooms(keys: list):
    if not keys:
        return {}
    async with get_async_db() as db:
        query = text("""
            SELECT product_id, product_name, type_id
            FROM `products`
            WHERE type_id IN :keys
        """).bindparams(bindparam("keys", expanding=True))
        result = await db.execute(query, {"keys": list(keys)})
        rooms = result.fetchall()
        return { room.type_id: room.product_name for room in rooms}


@router.get(
    "/pricing/{sub_hotel_id}",
    tags=["Pricing"],
    response_model=ResponseWrapper[PricingRequest],
    summary="Predict pricing for 30 days.",
    status_code=200,
)
async def predict_pricing(sub_hotel_id: int = Path(..., description="The ID of the sub-hotel to predict pricing for")) -> ResponseWrapper[PricingRequest]:
    logger.info(f"Started GET /pricing/{sub_hotel_id}")
    if not sub_hotel_id:
        raise HTTPException(status_code=400, detail="Sub-hotel ID is required")

    global _cache, _cache_date
    global _competitors_cache, _competitors_cache_date
    global _rooms_cache, _rooms_cache_date

    today = datetime.now(ZoneInfo("America/New_York")).date()

    async with _cache_lock:
        # Reset all caches on new day
        if _cache_date != today:
            _cache = {}
            _cache_date = today
            # Also reset rooms since the day changed
            _rooms_cache = {}
            _rooms_cache_date = today

        if _competitors_cache_date != today:
            _competitors_cache = None
            _competitors_cache_date = today

        # Pricing cache (per sub hotel)
        result = _cache.get(sub_hotel_id)
        if result is None:
            try:
                result = await predict_30_days_data(sub_hotel_id)
            except Exception as e:
                logger.error(f"ML prediction failed for sub_hotel_id={sub_hotel_id}: {e}", exc_info=True)
                raise HTTPException(status_code=500, detail="Price prediction failed. Please try again later.")
            _cache[sub_hotel_id] = result

        # Competitor cache (shared across sub hotels) — use `is None` not falsy check
        if _competitors_cache is None:
            try:
                _competitors_cache = await get_competitors_prices(days=7)
            except Exception as e:
                logger.error(f"Competitor price fetch failed: {e}", exc_info=True)
                _competitors_cache = {}  # Degrade gracefully with empty competitors

        competitors = _competitors_cache

    # Adjust predictions using competitor prices
    # result = await adjust_predictions_with_competitors(
    #     results=result,
    #     competitors=competitors,
    # )

    result = await apply_holidays(result)

    result = await apply_events(result)

    # Cache rooms lookup
    keys = list(result.keys())
    cache_key = tuple(sorted(keys))

    async with _cache_lock:
        rooms = _rooms_cache.get(cache_key)

    if rooms is None:
        rooms = await fetch_rooms(keys)
        async with _cache_lock:
            _rooms_cache[cache_key] = rooms

    return ResponseWrapper(status=True, message="OK", data=PricingRequest(prices=result, rooms=rooms, competitors=competitors))
