import os
import logging
from contextlib import asynccontextmanager
from typing import Optional

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware

from backend.models import (
    FeedImportPayload, StoredSignalRequest, FeedGeneratorDescription,
    LikeFeedState, StoredFeedState, GeneratorDefinition,
    FeedSkeletonResponse, OAuthConfig
)
from backend.services import (
    DemoFeedCatalog, SqliteFeedStore, FeedGeneratorService,
    FeedRankingService, TextEmbeddingService, FeedImportService,
    FeedRequestIdentityResolver, OAuthOriginResolver
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

services = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    os.makedirs("app_data", exist_ok=True)

    embedding_service = TextEmbeddingService()
    store = SqliteFeedStore()

    services["embedding"] = embedding_service
    services["store"] = store
    services["catalog"] = DemoFeedCatalog()
    services["ranking"] = FeedRankingService(embedding_service)
    services["generator"] = FeedGeneratorService()
    services["import"] = FeedImportService(store, embedding_service)
    services["identity"] = FeedRequestIdentityResolver()

    logger.info("Services initialized")
    yield
    logger.info("Shutting down")


app = FastAPI(
    title="Bluesky Feed Dashboard",
    version="0.1.0",
    lifespan=lifespan
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# Static file routes (explicit)
@app.get("/app.css")
async def get_app_css():
    return FileResponse("wwwroot/app.css")


@app.get("/preact-app.css")
async def get_preact_css():
    return FileResponse("wwwroot/preact-app.css")


@app.get("/js/{path:path}")
async def get_js(path: str):
    return FileResponse(f"wwwroot/js/{path}")


# API Routes
@app.get("/api/oauth/config")
async def get_oauth_config(request: Request):
    http_context = {
        "scheme": request.url.scheme,
        "host": request.headers.get("host", str(request.url.hostname))
    }
    origin = OAuthOriginResolver.resolve(http_context, {})

    return OAuthConfig(
        public_origin=origin,
        is_configured=bool(origin),
        allow_local_dev=True,
        current_origin=f"{http_context['scheme']}://{http_context['host']}"
    )


@app.get("/oauth/client-metadata.json")
async def get_client_metadata(request: Request):
    http_context = {
        "scheme": request.url.scheme,
        "host": request.headers.get("host", str(request.url.hostname))
    }
    origin = OAuthOriginResolver.resolve(http_context, {})

    if not origin:
        raise HTTPException(status_code=400, detail="BLUESKY_OAUTH_PUBLIC_ORIGIN not set")

    return {
        "client_id": f"{origin}/oauth/client-metadata.json",
        "client_name": "Bluesky Feed Dashboard",
        "client_uri": origin,
        "redirect_uris": [f"{origin}/oauth/callback"],
        "grant_types": ["authorization_code", "refresh_token"],
        "response_types": ["code"],
        "application_type": "web",
        "token_endpoint_auth_method": "none",
        "scope": "atproto transition:generic",
        "dpop_bound_access_tokens": True
    }


@app.get("/.well-known/did.json")
async def get_did_document():
    try:
        doc = services["generator"].get_did_document()
        return doc.model_dump(by_alias=True)
    except Exception as e:
        raise HTTPException(status_code=503, detail=str(e))


@app.get("/api/feed-generators")
async def list_generators() -> list[GeneratorDefinition]:
    return services["catalog"].list()


@app.get("/api/feed-generator-info")
async def get_generator_info() -> FeedGeneratorDescription:
    return services["generator"].describe()


@app.get("/api/feed-state")
async def get_feed_state(actorDid: Optional[str] = None, generatorId: str = "balanced") -> StoredFeedState:
    definition = services["catalog"].resolve(generatorId)
    if not definition:
        raise HTTPException(status_code=404, detail="Generator not found")

    return services["store"].get_state(actorDid, generatorId, services["ranking"], definition)


@app.get("/api/like-feed")
async def get_like_feed(actorDid: Optional[str] = None) -> LikeFeedState:
    return services["store"].get_like_feed(actorDid)


@app.get("/xrpc/app.bsky.feed.describeFeedGenerator")
async def describe_feed_generator():
    try:
        return services["generator"].describe_for_xrpc()
    except Exception as e:
        raise HTTPException(status_code=503, detail=str(e))


@app.get("/xrpc/app.bsky.feed.getFeedSkeleton")
async def get_feed_skeleton(feed: str, actor: Optional[str] = None, limit: Optional[int] = 50, cursor: Optional[str] = None):
    try:
        return services["generator"].get_skeleton(feed, actor, limit, cursor, store=services["store"])
    except Exception as e:
        error_msg = str(e).lower()
        if "not found" in error_msg:
            raise HTTPException(status_code=404, detail=str(e))
        elif any(x in error_msg for x in ["auth", "invalid", "expired"]):
            raise HTTPException(status_code=401, detail=str(e))
        raise HTTPException(status_code=503, detail=str(e))


@app.post("/api/feed-import")
async def import_feed(request: FeedImportPayload):
    try:
        await services["import"].import_async(request)
        return {"imported": True}
    except Exception as e:
        logger.error(f"Import failed: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/feed-signals")
async def add_signal(request: StoredSignalRequest) -> StoredFeedState:
    definition = services["catalog"].resolve(request.signal.generator_id)
    if not definition:
        raise HTTPException(status_code=404, detail="Generator not found")

    services["store"].add_signal(request.actor_did, request.signal)
    return services["store"].get_state(request.actor_did, request.signal.generator_id, services["ranking"], definition)


# SPA catch-all (must be last)
@app.get("/{full_path:path}", response_class=HTMLResponse)
async def serve_spa(full_path: str):
    # API and special routes should 404
    if full_path.startswith(("api/", "xrpc/", ".well-known/")):
        raise HTTPException(status_code=404)
    
    # OAuth metadata endpoints should 404 (not the callback)
    if full_path.startswith("oauth/") and not full_path.startswith("oauth/callback"):
        raise HTTPException(status_code=404)

    return """<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <title>Bluesky Feed Dashboard</title>
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="preact-app.css" />
</head>
<body>
    <div id="app"></div>
    <script type="module" src="js/app.js"></script>
</body>
</html>"""
An unhandled error has occurred. Reload 🗙