import json
import logging
import os
import sys
import time
from contextlib import asynccontextmanager
from functools import partial

from dotenv import load_dotenv

load_dotenv()

# Enable LangSmith tracing when API key is set 
if os.getenv("LANGSMITH_API_KEY"):
    os.environ.setdefault("LANGCHAIN_TRACING_V2", "true")
    os.environ.setdefault("LANGCHAIN_PROJECT", "user-profiling")

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from starlette.concurrency import run_in_threadpool
from starlette.middleware.cors import CORSMiddleware

from core.db_connection import db_session, verify_database_connectivity
from core.logger_config import setup_logging
from profiling_orchestrator import ProfilingOrchestrator, sanitize_profile_for_api
from storage.db_repo import get_profiling_repository
from storage.user_profiling import ensure_profiling_storage_tables

# Console: ERROR by default (quiet API). Set LOG_LEVEL=INFO or DEBUG for pipeline detail.
_log_level_name = (os.getenv("LOG_LEVEL") or "ERROR").upper()
_log_level = getattr(logging, _log_level_name, logging.ERROR)
setup_logging(level=_log_level)

logger = logging.getLogger(__name__)

# HTTP / OpenAI client chatter stays quiet; Uvicorn startup + access log are wired below (own handler).
for _noisy in ("httpx", "httpcore", "openai"):
    logging.getLogger(_noisy).setLevel(logging.WARNING)


def _configure_uvicorn_terminal_logging() -> None:
    # Keep Uvicorn INFO on its own handler when root is ERROR.
    uvilog = logging.getLogger("uvicorn")
    if any(getattr(h, "_profiling_uvicorn", False) for h in uvilog.handlers):
        return
    h = logging.StreamHandler(sys.stderr)
    h.setLevel(logging.INFO)
    h.setFormatter(logging.Formatter("%(levelname)s:\t%(message)s"))
    h._profiling_uvicorn = True  # type: ignore[attr-defined]
    uvilog.addHandler(h)
    uvilog.setLevel(logging.INFO)
    uvilog.propagate = False


_configure_uvicorn_terminal_logging()


def _cors_allow_origins() -> tuple[list[str], bool]:
    # Empty or * → any origin, no credentials. Comma list → restrict + credentials.
    raw = (os.getenv("CORS_ORIGINS") or "").strip()
    if not raw or raw == "*":
        return (["*"], False)
    origins = [o.strip() for o in raw.split(",") if o.strip()]
    if not origins:
        return (["*"], False)
    return (origins, True)


orchestrator: ProfilingOrchestrator | None = None


@asynccontextmanager
async def lifespan(_app: FastAPI):
    global orchestrator
    await run_in_threadpool(verify_database_connectivity)
    await run_in_threadpool(lambda: ensure_profiling_storage_tables(db_session))
    orchestrator = ProfilingOrchestrator(db_session)
    yield


def _require_orchestrator() -> ProfilingOrchestrator:
    o = orchestrator
    if o is None:
        raise HTTPException(
            status_code=503,
            detail="Service not ready (orchestrator not initialized).",
        )
    return o


app = FastAPI(
    title="User profiling API",
    version="1.2.0",
    lifespan=lifespan,
    openapi_tags=[{"name": "profile"}],
)

_cors_origins, _cors_credentials = _cors_allow_origins()
app.add_middleware(
    CORSMiddleware,
    allow_origins=_cors_origins,
    allow_credentials=_cors_credentials,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    allow_headers=["*"],
)


@app.get("/")
def read_root():
    return {"message": "User profiling API is online.", "docs": "/docs"}


@app.get("/profile/{user_id}", tags=["profile"])
async def get_user_profile(user_id: int):
    try:
        snap = await run_in_threadpool(
            lambda: get_profiling_repository().get_snapshot(user_id)
        )
        if not snap or not (snap.profile_json or "").strip():
            raise HTTPException(
                status_code=404,
                detail=f"No stored profile for user_id={user_id}.",
            )
        try:
            profile = json.loads(snap.profile_json)
        except json.JSONDecodeError:
            logger.exception("GET /profile/%s: invalid profile_json in DB", user_id)
            raise HTTPException(
                status_code=500,
                detail="Stored profile data is corrupted (invalid JSON).",
            ) from None
        if not profile.get("metadata"):
            raise HTTPException(
                status_code=404,
                detail=f"No usable profile for user_id={user_id}.",
            )
        sanitize_profile_for_api(profile)
        md = (snap.deep_analysis_markdown or "").strip()
        profile["deep_analysis"] = md if md else None
        return profile
    except HTTPException:
        raise
    except Exception as e:
        logger.exception("GET /profile/%s failed", user_id)
        raise HTTPException(status_code=500, detail="Internal server error") from e


class CreateProfilingRequest(BaseModel):
    user_id: int
    days: int = Field(default=60, ge=1, le=180)


@app.post("/profile/create_profiling", tags=["profile"])
async def create_user_profiling(body: CreateProfilingRequest):
    user_id = body.user_id
    days = body.days
    try:
        orch = _require_orchestrator()
        t0 = time.perf_counter()
        profile = await run_in_threadpool(
            partial(
                orch.generate_user_profile_data,
                user_id=user_id,
                window_days=days,
            )
        )
        if not profile or not profile.get("metadata"):
            raise HTTPException(
                status_code=404,
                detail=f"User {user_id} profile could not be generated (missing data or user).",
            )
        markdown_report = await run_in_threadpool(
            partial(orch.run_deep_analysis, user_id, profile)
        )
        logger.debug(
            "POST /profile/create_profiling user_id=%s deep_analysis done in %.2fs",
            user_id,
            time.perf_counter() - t0,
        )
        return {"deep_analysis": markdown_report}
    except HTTPException:
        raise
    except Exception as e:
        logger.exception(
            "POST /profile/create_profiling failed (user_id=%s, days=%s)", user_id, days
        )
        raise HTTPException(status_code=500, detail="Internal server error") from e


if __name__ == "__main__":
    # Dev: python main.py | Prod: gunicorn main:app -k uvicorn.workers.UvicornWorker ...
    import uvicorn

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=5009,
        log_level="info",
        access_log=True,
    )
