from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
from typing import Optional
import asyncio
import aiohttp
from aiohttp import CookieJar
import os
import requests
import datetime
from pathlib import Path

from cookidoo_api import (
    CookidooParseException,
    Cookidoo,
    CookidooConfig,
    CookidooLocalizationConfig,
    CookidooException,
    CookidooAuthException,
    CookidooRequestException,
)

app = FastAPI(title="Cookidoo API", version="1.0.0")

# ---------------------------------------------------------------------------
# Configuration — override via environment variables or .env
# ---------------------------------------------------------------------------
COOKIDOO_EMAIL    = os.environ.get("COOKIDOO_EMAIL", "your@email.it")
COOKIDOO_PASSWORD = os.environ.get("COOKIDOO_PASSWORD", "yourpassword")
COOKIE_FILE       = Path(os.environ.get("COOKIE_FILE", "/opt/cookidoo-api/.cookidoo_cookies"))

# Italy locale — change country_code/language/url for other regions
LOCALIZATION = CookidooLocalizationConfig(
    country_code="it",
    language="it-IT",
    url="https://cookidoo.it/foundation/it-IT",
)

# ---------------------------------------------------------------------------
# Session cache — one authenticated session reused across requests
# ---------------------------------------------------------------------------
_session_cache: dict = {
    "api": None,
    "session": None,
    "jar": None,
}


async def _get_api() -> Cookidoo:
    """Return a cached, authenticated Cookidoo API instance, logging in if needed."""
    if _session_cache["api"] is not None:
        return _session_cache["api"]

    jar = CookieJar(unsafe=True)
    session = aiohttp.ClientSession(cookie_jar=jar)

    cfg = CookidooConfig(
        localization=LOCALIZATION,
        email=COOKIDOO_EMAIL,
        password=COOKIDOO_PASSWORD,
    )
    api = Cookidoo(session, cfg)

    if COOKIE_FILE.exists():
        try:
            api.load_cookies(COOKIE_FILE)
        except Exception:
            pass

    try:
        await api.login()
        api.save_cookies(COOKIE_FILE)
    except CookidooParseException:
        if COOKIE_FILE.exists():
            COOKIE_FILE.unlink()
        try:
            await api.login()
            api.save_cookies(COOKIE_FILE)
        except Exception as e:
            await session.close()
            raise HTTPException(status_code=503, detail=f"Login failed: {e}")
    except CookidooAuthException as e:
        await session.close()
        raise HTTPException(status_code=401, detail=f"Login failed: {e}")

    _session_cache.update({"api": api, "session": session, "jar": jar})
    return api


async def _reset_session():
    """Tear down the cached session so the next call triggers a fresh login."""
    if _session_cache["session"]:
        await _session_cache["session"].close()
    _session_cache.update({"api": None, "session": None, "jar": None})
    if COOKIE_FILE.exists():
        COOKIE_FILE.unlink()


# ---------------------------------------------------------------------------
# Webhook helper (same pattern as yt-dlp API)
# ---------------------------------------------------------------------------
def _send_webhook(webhook_url: str, payload: dict):
    try:
        requests.post(webhook_url, json=payload, timeout=10,
                      headers={"Content-Type": "application/json"})
    except Exception as e:
        print(f"Webhook error: {e}")


# ---------------------------------------------------------------------------
# Pydantic models
# ---------------------------------------------------------------------------
class RecipeRequest(BaseModel):
    recipe_id: str
    webhook_url: Optional[str] = None
    reference_id: Optional[str] = None

class RecipesRequest(BaseModel):
    recipe_ids: list[str]
    webhook_url: Optional[str] = None
    reference_id: Optional[str] = None

class AdditionalItemsRequest(BaseModel):
    names: list[str]
    webhook_url: Optional[str] = None
    reference_id: Optional[str] = None

class ServingRequest(BaseModel):
    recipe_id: str
    serving_size: int = 4
    webhook_url: Optional[str] = None
    reference_id: Optional[str] = None

class CalendarRequest(BaseModel):
    day: str  # ISO format: "2025-12-25"
    recipe_ids: list[str]
    webhook_url: Optional[str] = None
    reference_id: Optional[str] = None


# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@app.get("/")
def read_root():
    return {
        "message": "Cookidoo API is running",
        "localization": {
            "country_code": LOCALIZATION.country_code,
            "language": LOCALIZATION.language,
        },
        "endpoints": {
            # Auth
            "POST /auth/refresh":              "Force a new login and refresh session cookies",
            # User
            "GET  /user/info":                 "Get account info",
            "GET  /user/subscription":         "Get active subscription details",
            # Shopping list
            "GET  /shopping/ingredients":      "Get all ingredient items on the shopping list",
            "GET  /shopping/additional-items": "Get manually added items on the shopping list",
            "GET  /shopping/recipes":          "Get recipes currently on the shopping list",
            "POST /shopping/add-recipe":       "Add a recipe's ingredients to the shopping list",
            "POST /shopping/add-recipes":      "Add multiple recipes at once",
            "POST /shopping/add-items":        "Add free-text items to the shopping list",
            "DELETE /shopping/clear":          "Clear the entire shopping list",
            # Recipes
            "POST /recipes/details":           "Get full details of a recipe",
            "POST /recipes/add-custom":        "Copy a public recipe into your account",
            # Collections
            "GET  /collections/custom":        "List your custom collections",
            "GET  /collections/managed":       "List managed (official) collections",
            # Calendar
            "POST /calendar/add":              "Add recipes to a calendar day",
            "GET  /calendar/week":             "Get recipes planned for a given week",
        },
    }


# --- Auth -------------------------------------------------------------------

@app.post("/auth/refresh")
async def auth_refresh():
    """Force a fresh login, discarding any cached session."""
    await _reset_session()
    await _get_api()
    return {"status": "ok", "message": "Session refreshed successfully"}


# --- User -------------------------------------------------------------------

@app.get("/user/info")
async def user_info():
    api = await _get_api()
    try:
        info = await api.get_user_info()
        return info
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/user/subscription")
async def user_subscription():
    api = await _get_api()
    try:
        sub = await api.get_active_subscription()
        return sub or {"message": "No active subscription found"}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


# --- Shopping list ----------------------------------------------------------

@app.get("/shopping/ingredients")
async def shopping_ingredients():
    api = await _get_api()
    try:
        items = await api.get_ingredient_items()
        return {"count": len(items), "items": items}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/shopping/additional-items")
async def shopping_additional_items():
    api = await _get_api()
    try:
        items = await api.get_additional_items()
        return {"count": len(items), "items": items}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/shopping/recipes")
async def shopping_recipes():
    api = await _get_api()
    try:
        recipes = await api.get_shopping_list_recipes()
        return {"count": len(recipes), "recipes": recipes}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/shopping/add-recipe")
async def shopping_add_recipe(req: RecipeRequest, background_tasks: BackgroundTasks):
    api = await _get_api()
    try:
        items = await api.add_ingredient_items_for_recipes([req.recipe_id])
        result = {"status": "ok", "recipe_id": req.recipe_id,
                  "items_added": len(items), "items": items,
                  "reference_id": req.reference_id}
        if req.webhook_url:
            background_tasks.add_task(_send_webhook, req.webhook_url, result)
        return result
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/shopping/add-recipes")
async def shopping_add_recipes(req: RecipesRequest, background_tasks: BackgroundTasks):
    api = await _get_api()
    try:
        items = await api.add_ingredient_items_for_recipes(req.recipe_ids)
        result = {"status": "ok", "recipe_ids": req.recipe_ids,
                  "items_added": len(items), "items": items,
                  "reference_id": req.reference_id}
        if req.webhook_url:
            background_tasks.add_task(_send_webhook, req.webhook_url, result)
        return result
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/shopping/add-items")
async def shopping_add_items(req: AdditionalItemsRequest, background_tasks: BackgroundTasks):
    api = await _get_api()
    try:
        items = await api.add_additional_items(req.names)
        result = {"status": "ok", "items_added": len(items), "items": items,
                  "reference_id": req.reference_id}
        if req.webhook_url:
            background_tasks.add_task(_send_webhook, req.webhook_url, result)
        return result
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.delete("/shopping/clear")
async def shopping_clear():
    api = await _get_api()
    try:
        await api.clear_shopping_list()
        return {"status": "ok", "message": "Shopping list cleared"}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


# --- Recipes ----------------------------------------------------------------

@app.post("/recipes/details")
async def recipe_details(req: RecipeRequest):
    api = await _get_api()
    try:
        details = await api.get_recipe_details(req.recipe_id)
        return details
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/recipes/add-custom")
async def recipe_add_custom(req: ServingRequest, background_tasks: BackgroundTasks):
    api = await _get_api()
    try:
        recipe = await api.add_custom_recipe_from(req.recipe_id, req.serving_size)
        result = {"status": "ok", "recipe": recipe, "reference_id": req.reference_id}
        if req.webhook_url:
            background_tasks.add_task(_send_webhook, req.webhook_url, result)
        return result
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


# --- Collections ------------------------------------------------------------

@app.get("/collections/custom")
async def collections_custom(page: int = 0):
    api = await _get_api()
    try:
        cols = await api.get_custom_collections(page=page)
        return {"count": len(cols), "page": page, "collections": cols}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/collections/managed")
async def collections_managed(page: int = 0):
    api = await _get_api()
    try:
        cols = await api.get_managed_collections(page=page)
        return {"count": len(cols), "page": page, "collections": cols}
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


# --- Calendar ---------------------------------------------------------------

@app.post("/calendar/add")
async def calendar_add(req: CalendarRequest, background_tasks: BackgroundTasks):
    api = await _get_api()
    try:
        day = datetime.date.fromisoformat(req.day)
        result_day = await api.add_recipes_to_calendar(day, req.recipe_ids)
        result = {"status": "ok", "day": req.day, "calendar_day": result_day,
                  "reference_id": req.reference_id}
        if req.webhook_url:
            background_tasks.add_task(_send_webhook, req.webhook_url, result)
        return result
    except ValueError:
        raise HTTPException(status_code=422, detail="day must be ISO format: YYYY-MM-DD")
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/calendar/week")
async def calendar_week(day: str):
    """Get planned recipes for the week containing the given day (ISO: YYYY-MM-DD)."""
    api = await _get_api()
    try:
        d = datetime.date.fromisoformat(day)
        week = await api.get_recipes_in_calendar_week(d)
        return {"day": day, "week": week}
    except ValueError:
        raise HTTPException(status_code=422, detail="day must be ISO format: YYYY-MM-DD")
    except CookidooAuthException:
        await _reset_session()
        raise HTTPException(status_code=401, detail="Session expired — call /auth/refresh")
    except CookidooRequestException as e:
        msg = str(e)
        if "authorization" in msg.lower() or "unauthorized" in msg.lower():
            await _reset_session()
            raise HTTPException(status_code=401, detail=f"Session expired — call /auth/refresh. Detail: {msg}")
        raise HTTPException(status_code=502, detail=f"Cookidoo request failed: {msg}")
    except CookidooException as e:
        raise HTTPException(status_code=500, detail=str(e))



# --- Debug ---------------------------------------------------------------

@app.get("/debug/collections-raw")
async def debug_collections_raw():
    api = await _get_api()
    import aiohttp
    # Chiama direttamente l'endpoint raw per vedere la risposta grezza
    from cookidoo_api.const import CUSTOM_COLLECTIONS_PATH, CUSTOM_COLLECTIONS_PATH_ACCEPT, MANAGED_COLLECTIONS_PATH, MANAGED_COLLECTIONS_PATH_ACCEPT
    results = {}
    for name, path, accept in [
        ("custom",  CUSTOM_COLLECTIONS_PATH,  CUSTOM_COLLECTIONS_PATH_ACCEPT),
        ("managed", MANAGED_COLLECTIONS_PATH, MANAGED_COLLECTIONS_PATH_ACCEPT),
    ]:
        url = api.api_endpoint / path.format(**api._cfg.localization.__dict__)
        async with api._session.get(url, headers={**api._api_headers, "ACCEPT": accept}, params={"page": 0}) as r:
            results[name] = {"status": r.status, "body": await r.text()}
    return results


# --- Health -----------------------------------------------------------------

@app.get("/health")
def health_check():
    return {
        "status": "healthy",
        "session_active": _session_cache["api"] is not None,
        "cookie_file_exists": COOKIE_FILE.exists(),
    }
