# backend/auth.py
"""
JWT authentication for FastAPI (cookie-based).

Environment (.env):
  ADMIN_USER=admin@ardem.com
  AUTH_USERS=second.user@ardem.com,another.user@ardem.com  # optional
  ADMIN_HASHED_PW=<bcrypt-hash-from-passlib>
  JWT_SECRET=<long-random-string>
  JWT_ALG=HS256
  ACCESS_TOKEN_EXPIRE_MIN=480
  AUTH_COOKIE_SECURE=false    # set true in HTTPS prod

Usage in app.py:
  from auth import router as auth_router, require_user
  app.include_router(auth_router, prefix="/auth", tags=["auth"])

  @app.get("/api/secure-endpoint")
  async def secure_endpoint(user: str = Depends(require_user)):
      return {"hello": user}
"""

import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from jose import jwt, JWTError
from passlib.hash import bcrypt
from pydantic import BaseModel
from dotenv import load_dotenv

# Load the backend environment file, independent of the process working dir.
# Explicit service-level environment variables still take precedence.
load_dotenv(dotenv_path=Path(__file__).with_name(".env"), override=False)

# ----- Config -----
ADMIN_USER = os.getenv("ADMIN_USER", "admin@ardem.com")
ADMIN_HASHED_PW = os.getenv("ADMIN_HASHED_PW", "")
AUTH_USERS = {
    user.strip().lower()
    for user in os.getenv("AUTH_USERS", "").replace(";", ",").split(",")
    if user.strip()
}
AUTH_USERS.add(ADMIN_USER.strip().lower())
JWT_SECRET = os.getenv("JWT_SECRET", "dev-secret-change-me")
JWT_ALG = os.getenv("JWT_ALG", "HS256")
ACCESS_TOKEN_EXPIRE_MIN = int(os.getenv("ACCESS_TOKEN_EXPIRE_MIN", "480"))

COOKIE_NAME = "access_token"
COOKIE_HTTPONLY = True
COOKIE_SAMESITE = "lax"  # use "none" (with HTTPS) if frontend is on a different domain
COOKIE_SECURE = os.getenv("AUTH_COOKIE_SECURE", "false").lower() == "true"  # set True in HTTPS

router = APIRouter()


# ----- Models -----
class LoginBody(BaseModel):
    username: str
    password: str


# ----- Helpers -----
def create_access_token(sub: str, minutes: int = ACCESS_TOKEN_EXPIRE_MIN) -> str:
    now = datetime.now(tz=timezone.utc)
    payload = {
        "sub": sub,
        "iat": int(now.timestamp()),
        "exp": int((now + timedelta(minutes=minutes)).timestamp()),
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)


def decode_token(token: str) -> dict:
    return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])


def verify_password(plain: str, hashed: str) -> bool:
    if not hashed:
        return False
    try:
        return bcrypt.verify(plain, hashed)
    except Exception:
        return False


def _get_user_from_cookie(request: Request) -> Optional[str]:
    token = request.cookies.get(COOKIE_NAME)
    if not token:
        return None
    try:
        payload = decode_token(token)
        return payload.get("sub")
    except JWTError:
        return None


async def require_user(request: Request) -> str:
    """
    Dependency to protect routes.
    Use: `user: str = Depends(require_user)`
    """
    user = _get_user_from_cookie(request)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated"
        )
    return user


def _set_auth_cookie(response: Response, token: str) -> None:
    response.set_cookie(
        key=COOKIE_NAME,
        value=token,
        max_age=ACCESS_TOKEN_EXPIRE_MIN * 60,
        httponly=COOKIE_HTTPONLY,
        samesite=COOKIE_SAMESITE,
        secure=COOKIE_SECURE,
        path="/",
    )


def _clear_auth_cookie(response: Response) -> None:
    response.delete_cookie(
        key=COOKIE_NAME,
        path="/",
        httponly=COOKIE_HTTPONLY,
        samesite=COOKIE_SAMESITE,
        secure=COOKIE_SECURE,
    )


# ----- Routes -----
@router.post("/login")
async def login(body: LoginBody, response: Response):
    """
    Accepts JSON: { "username": "...", "password": "..." }
    On success: sets HttpOnly JWT cookie and returns { ok: true, user: "<email>" }
    On failure: 401 with "Invalid credentials"
    """
    username = (body.username or "").strip()
    password = body.password or ""

    # Constant-time-ish checks: same message for user or password mismatch
    if username.lower() not in AUTH_USERS or not verify_password(
        password, ADMIN_HASHED_PW
    ):
        raise HTTPException(status_code=401, detail="Invalid credentials")

    token = create_access_token(sub=username)
    _set_auth_cookie(response, token)
    return {"ok": True, "user": username}


@router.post("/logout")
async def logout(response: Response):
    _clear_auth_cookie(response)
    return {"ok": True}


@router.get("/me")
async def me(user: str = Depends(require_user)):
    return {"user": user}
