"""Authentication REST API routes."""

import os
import time
import uuid
from datetime import datetime, timedelta, timezone

import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import RedirectResponse

from peon_mcp.auth.crypto import decrypt_token, encrypt_token
from peon_mcp.auth.schemas import AuthMeResponse, AuthStatusResponse, GitHubRepo, GitHubUser
from peon_mcp.common.dependencies import get_db

router = APIRouter(tags=["Auth"])

# ---------------------------------------------------------------------------
# In-memory CSRF state store with TTL
# ---------------------------------------------------------------------------

_STATE_TTL = 300  # 5 minutes
_pending_states: dict[str, tuple[float, str]] = {}  # state -> (expiry, redirect_to)


def _store_state(state: str, redirect_to: str = "/") -> None:
    """Store a CSRF state with TTL and post-login redirect URL."""
    now = time.time()
    # Lazy cleanup of expired states
    expired = [k for k, v in _pending_states.items() if v[0] < now]
    for k in expired:
        _pending_states.pop(k, None)
    _pending_states[state] = (now + _STATE_TTL, redirect_to)


def _pop_state(state: str) -> tuple[bool, str]:
    """Validate and consume a CSRF state. Returns (valid, redirect_to)."""
    entry = _pending_states.pop(state, None)
    if entry is None:
        return False, "/"
    expiry, redirect_to = entry
    return time.time() < expiry, redirect_to


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"
GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"
GITHUB_USER_URL = "https://api.github.com/user"

SESSION_COOKIE = "peon_session"
SESSION_MAX_AGE = 30 * 24 * 3600  # 30 days in seconds


def _get_github_client_id() -> str:
    val = os.environ.get("GITHUB_APP_CLIENT_ID", "").strip()
    if not val:
        raise HTTPException(500, detail="GITHUB_APP_CLIENT_ID is not configured")
    return val


def _get_github_client_secret() -> str:
    val = os.environ.get("GITHUB_APP_CLIENT_SECRET", "").strip()
    if not val:
        raise HTTPException(500, detail="GITHUB_APP_CLIENT_SECRET is not configured")
    return val


def _callback_url(request: Request) -> str:
    """Return the OAuth callback URL."""
    explicit = os.environ.get("PEON_AUTH_CALLBACK_URL")
    if explicit:
        return explicit
    return str(request.url_for("github_callback"))


def _is_localhost(request: Request) -> bool:
    host = request.url.hostname or ""
    return host in ("localhost", "127.0.0.1", "0.0.0.0")


def _cookie_kwargs(request: Request) -> dict:
    localhost = _is_localhost(request)
    # Non-localhost deployments typically serve the UI and API on different
    # origins (e.g. peon.app vs api.peon.app). SameSite=none is required so
    # the browser sends the cookie on cross-origin fetches with credentials.
    # SameSite=none requires Secure=true.
    return {
        "key": SESSION_COOKIE,
        "httponly": True,
        "samesite": "lax" if localhost else "none",
        "secure": not localhost,
        "max_age": SESSION_MAX_AGE,
        "path": "/",
    }


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------


@router.get("/api/auth/status", response_model=AuthStatusResponse)
async def auth_status(request: Request, db=Depends(get_db)):
    """Public endpoint: returns configured auth methods and current user if authenticated."""
    methods: list[str] = []
    if os.environ.get("PEON_API_KEY"):
        methods.append("api_key")
    if os.environ.get("GITHUB_APP_CLIENT_ID"):
        methods.append("github_oauth")

    user = None
    session_id = request.cookies.get(SESSION_COOKIE)
    if session_id:
        row = await db.execute_fetchone(
            "SELECT u.github_id, u.login, u.avatar_url, u.name, u.email "
            "FROM user_sessions s JOIN users u ON s.user_id = u.id "
            "WHERE s.session_token = ? AND s.expires_at > NOW()",
            (session_id,),
        )
        if row:
            user = GitHubUser(
                id=row["github_id"],
                login=row["login"],
                avatar_url=row["avatar_url"] or "",
                name=row["name"] or "",
                email=row["email"] or "",
            )

    return AuthStatusResponse(
        authenticated=user is not None,
        auth_methods=methods,
        user=user,
    )


@router.get("/api/auth/github")
async def github_login(request: Request, redirect_to: str = "/"):
    """Redirect to GitHub OAuth authorization page."""
    client_id = _get_github_client_id()
    state = uuid.uuid4().hex
    _store_state(state, redirect_to)
    callback = _callback_url(request)
    url = (
        f"{GITHUB_AUTHORIZE_URL}"
        f"?client_id={client_id}"
        f"&redirect_uri={callback}"
        f"&scope=repo,read:org"
        f"&state={state}"
    )
    return RedirectResponse(url=url, status_code=307)


@router.get("/api/auth/github/callback")
async def github_callback(
    request: Request,
    code: str,
    state: str,
    db=Depends(get_db),
):
    """Handle GitHub OAuth callback: exchange code for tokens, upsert user, create session."""
    valid, redirect_to = _pop_state(state)
    if not valid:
        raise HTTPException(400, detail="Invalid or expired state parameter")

    client_id = _get_github_client_id()
    client_secret = _get_github_client_secret()

    # Exchange code for tokens
    async with httpx.AsyncClient() as client:
        token_resp = await client.post(
            GITHUB_TOKEN_URL,
            data={
                "client_id": client_id,
                "client_secret": client_secret,
                "code": code,
            },
            headers={"Accept": "application/json"},
        )
        if token_resp.status_code != 200:
            raise HTTPException(502, detail="Failed to exchange code with GitHub")
        token_data = token_resp.json()

    access_token = token_data.get("access_token")
    if not access_token:
        error = token_data.get("error_description", token_data.get("error", "unknown"))
        raise HTTPException(400, detail=f"GitHub OAuth error: {error}")

    refresh_token = token_data.get("refresh_token")
    expires_in = token_data.get("expires_in")

    # Fetch user info
    async with httpx.AsyncClient() as client:
        user_resp = await client.get(
            GITHUB_USER_URL,
            headers={
                "Authorization": f"Bearer {access_token}",
                "Accept": "application/json",
            },
        )
        if user_resp.status_code != 200:
            raise HTTPException(502, detail="Failed to fetch GitHub user info")
        user_data = user_resp.json()

    github_id = user_data["id"]
    login = user_data.get("login", "")
    avatar_url = user_data.get("avatar_url", "")
    name = user_data.get("name", "") or ""
    email = user_data.get("email", "") or ""

    # Upsert user
    await db.execute(
        "INSERT INTO users (github_id, login, avatar_url, name, email) "
        "VALUES (?, ?, ?, ?, ?) "
        "ON CONFLICT (github_id) DO UPDATE SET "
        "login = EXCLUDED.login, avatar_url = EXCLUDED.avatar_url, "
        "name = EXCLUDED.name, email = EXCLUDED.email, updated_at = NOW()",
        (github_id, login, avatar_url, name, email),
    )
    await db.commit()

    # Get internal user id
    user_row = await db.execute_fetchone(
        "SELECT id FROM users WHERE github_id = ?", (github_id,)
    )
    user_id = user_row["id"]

    # Encrypt tokens
    encrypted_access = encrypt_token(access_token)
    encrypted_refresh = encrypt_token(refresh_token) if refresh_token else None

    # Compute token expiry
    token_expires_at = None
    if expires_in:
        token_expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(expires_in))

    session_expires_at = datetime.now(timezone.utc) + timedelta(seconds=SESSION_MAX_AGE)

    # Create session
    session_token = uuid.uuid4().hex
    await db.execute(
        "INSERT INTO user_sessions "
        "(user_id, session_token, encrypted_access_token, encrypted_refresh_token, "
        "token_expires_at, expires_at) "
        "VALUES (?, ?, ?, ?, ?, ?)",
        (user_id, session_token, encrypted_access, encrypted_refresh,
         token_expires_at, session_expires_at),
    )
    await db.commit()

    response = RedirectResponse(url=redirect_to, status_code=302)
    response.set_cookie(value=session_token, **_cookie_kwargs(request))
    return response


@router.get("/api/auth/me", response_model=AuthMeResponse)
async def auth_me(request: Request, db=Depends(get_db)):
    """Return the currently authenticated user's info."""
    session_id = request.cookies.get(SESSION_COOKIE)
    if not session_id:
        raise HTTPException(401, detail="Not authenticated")

    row = await db.execute_fetchone(
        "SELECT u.github_id, u.login, u.avatar_url, u.name, u.email, "
        "s.expires_at, s.token_expires_at "
        "FROM user_sessions s JOIN users u ON s.user_id = u.id "
        "WHERE s.session_token = ? AND s.expires_at > NOW()",
        (session_id,),
    )
    if not row:
        raise HTTPException(401, detail="Session expired or invalid")

    return AuthMeResponse(
        user=GitHubUser(
            id=row["github_id"],
            login=row["login"],
            avatar_url=row["avatar_url"] or "",
            name=row["name"] or "",
            email=row["email"] or "",
        ),
        session_expires_at=row["expires_at"],
        token_expires_at=row["token_expires_at"],
    )


@router.post("/api/auth/logout")
async def auth_logout(request: Request, db=Depends(get_db)):
    """Delete the current session and clear the cookie."""
    session_id = request.cookies.get(SESSION_COOKIE)
    if session_id:
        await db.execute(
            "DELETE FROM user_sessions WHERE session_token = ?",
            (session_id,),
        )
        await db.commit()

    response = {"ok": True}
    from fastapi.responses import JSONResponse

    resp = JSONResponse(content=response)
    resp.delete_cookie(key=SESSION_COOKIE, path="/")
    return resp


@router.post("/api/auth/refresh", response_model=AuthMeResponse)
async def auth_refresh(request: Request, db=Depends(get_db)):
    """Refresh the GitHub access token if it expires within 30 minutes."""
    session_id = request.cookies.get(SESSION_COOKIE)
    if not session_id:
        raise HTTPException(401, detail="Not authenticated")

    row = await db.execute_fetchone(
        "SELECT s.id as session_id, s.encrypted_refresh_token, s.token_expires_at, "
        "s.expires_at, s.user_id, "
        "u.github_id, u.login, u.avatar_url, u.name, u.email "
        "FROM user_sessions s JOIN users u ON s.user_id = u.id "
        "WHERE s.session_token = ? AND s.expires_at > NOW()",
        (session_id,),
    )
    if not row:
        raise HTTPException(401, detail="Session expired or invalid")

    # Check if refresh is needed (token expires within 30 minutes)
    token_expires_at = row["token_expires_at"]
    if token_expires_at and token_expires_at > datetime.now(timezone.utc) + timedelta(minutes=30):
        # Token still has plenty of time
        return AuthMeResponse(
            user=GitHubUser(
                id=row["github_id"],
                login=row["login"],
                avatar_url=row["avatar_url"] or "",
                name=row["name"] or "",
                email=row["email"] or "",
            ),
            session_expires_at=row["expires_at"],
            token_expires_at=token_expires_at,
        )

    if not row["encrypted_refresh_token"]:
        raise HTTPException(400, detail="No refresh token available")

    refresh_token = decrypt_token(row["encrypted_refresh_token"])
    client_id = _get_github_client_id()
    client_secret = _get_github_client_secret()

    async with httpx.AsyncClient() as client:
        token_resp = await client.post(
            GITHUB_TOKEN_URL,
            data={
                "client_id": client_id,
                "client_secret": client_secret,
                "grant_type": "refresh_token",
                "refresh_token": refresh_token,
            },
            headers={"Accept": "application/json"},
        )
        if token_resp.status_code != 200:
            raise HTTPException(502, detail="Failed to refresh token with GitHub")
        token_data = token_resp.json()

    new_access = token_data.get("access_token")
    if not new_access:
        error = token_data.get("error_description", token_data.get("error", "unknown"))
        raise HTTPException(400, detail=f"GitHub refresh error: {error}")

    new_refresh = token_data.get("refresh_token")
    new_expires_in = token_data.get("expires_in")

    encrypted_access = encrypt_token(new_access)
    encrypted_refresh = encrypt_token(new_refresh) if new_refresh else row["encrypted_refresh_token"]

    new_token_expires_at = None
    if new_expires_in:
        new_token_expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(new_expires_in))

    await db.execute(
        "UPDATE user_sessions SET encrypted_access_token = ?, encrypted_refresh_token = ?, "
        "token_expires_at = ? WHERE id = ?",
        (encrypted_access, encrypted_refresh, new_token_expires_at, row["session_id"]),
    )
    await db.commit()

    return AuthMeResponse(
        user=GitHubUser(
            id=row["github_id"],
            login=row["login"],
            avatar_url=row["avatar_url"] or "",
            name=row["name"] or "",
            email=row["email"] or "",
        ),
        session_expires_at=row["expires_at"],
        token_expires_at=new_token_expires_at,
    )


@router.get("/api/auth/github/repos", response_model=list[GitHubRepo])
async def github_repos(request: Request, page: int = 1, db=Depends(get_db)):
    """List the authenticated user's GitHub repositories."""
    session_id = request.cookies.get(SESSION_COOKIE)
    if not session_id:
        raise HTTPException(401, detail="Not authenticated")

    row = await db.execute_fetchone(
        "SELECT s.encrypted_access_token "
        "FROM user_sessions s "
        "WHERE s.session_token = ? AND s.expires_at > NOW()",
        (session_id,),
    )
    if not row:
        raise HTTPException(401, detail="Session expired or invalid")

    access_token = decrypt_token(row["encrypted_access_token"])

    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.github.com/user/repos",
            params={
                "per_page": 100,
                "sort": "updated",
                "type": "owner",
                "page": page,
            },
            headers={
                "Authorization": f"Bearer {access_token}",
                "Accept": "application/json",
            },
        )
        if resp.status_code != 200:
            raise HTTPException(502, detail="Failed to fetch repositories from GitHub")
        repos_data = resp.json()

    return [
        GitHubRepo(
            name=r["name"],
            full_name=r["full_name"],
            html_url=r["html_url"],
            description=r.get("description") or "",
            private=r.get("private", False),
            default_branch=r.get("default_branch", "main"),
        )
        for r in repos_data
    ]
