"""Authentication: user register/login (JWT in HttpOnly cookie), Google OAuth 2.0 flow."""
import json
import secrets
from datetime import datetime, timezone

import redis as redis_lib
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import HTMLResponse, RedirectResponse
from pydantic import BaseModel, EmailStr, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db, require_csrf
from app.core.config import settings
from app.core.security import (create_access_token, hash_password, log_audit,
                               verify_password)
from app.db.session import AsyncSessionLocal
from app.models import User
from app.services import gmail_service

router = APIRouter(prefix="/auth", tags=["auth"])

try:
    _oauth_redis = redis_lib.from_url(settings.REDIS_URL, socket_timeout=2)
except Exception:
    _oauth_redis = None


class RegisterIn(BaseModel):
    email: EmailStr
    password: str = Field(min_length=10, max_length=128)
    full_name: str = ""


class LoginIn(BaseModel):
    email: EmailStr
    password: str


def _set_session_cookie(response: Response, token: str) -> None:
    response.set_cookie(
        settings.SESSION_COOKIE_NAME, token,
        httponly=True, samesite="lax", secure=(settings.ENVIRONMENT == "production"),
        max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60, path="/",
    )
    response.set_cookie(
        settings.CSRF_COOKIE_NAME, secrets.token_urlsafe(24),
        httponly=False, samesite="lax", secure=(settings.ENVIRONMENT == "production"),
        max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60, path="/",
    )


@router.post("/register", status_code=201)
async def register(payload: RegisterIn, request: Request, response: Response,
                   db: AsyncSession = Depends(get_db)):
    existing = (await db.execute(select(User).where(User.email == payload.email.lower()))).scalar_one_or_none()
    if existing:
        raise HTTPException(409, "Email already registered")
    user = User(email=payload.email.lower(), password_hash=hash_password(payload.password),
                full_name=payload.full_name, is_admin=True)
    db.add(user)
    await db.commit()
    token = create_access_token(user.id)
    _set_session_cookie(response, token)
    await log_audit(db, user.id, "user.register", "user", user.id,
                    {"email": user.email}, request.client.host)
    return {"id": user.id, "email": user.email, "token": token}


@router.post("/login")
async def login(payload: LoginIn, request: Request, response: Response,
                db: AsyncSession = Depends(get_db)):
    user = (await db.execute(select(User).where(User.email == payload.email.lower()))).scalar_one_or_none()
    if not user or not verify_password(payload.password, user.password_hash):
        raise HTTPException(401, "Invalid email or password")
    if not user.is_active:
        raise HTTPException(403, "Account disabled")
    user.last_login_at = datetime.now(timezone.utc)
    await db.commit()
    token = create_access_token(user.id)
    _set_session_cookie(response, token)
    return {"id": user.id, "email": user.email, "token": token}


@router.post("/logout")
async def logout(response: Response, _: None = Depends(require_csrf)):
    response.delete_cookie(settings.SESSION_COOKIE_NAME)
    response.delete_cookie(settings.CSRF_COOKIE_NAME)
    return {"ok": True}


@router.get("/me")
async def me(user=Depends(get_current_user)):
    return {"id": user.id, "email": user.email, "full_name": user.full_name,
            "is_admin": user.is_admin}


# ---------------------------------------------------------------------------
# Google OAuth 2.0 (authorization code flow with state verification)
# ---------------------------------------------------------------------------
async def google_start_impl(user, client: dict = None):
    """Shared implementation: generate state, persist, return Google's authorization URL.
    `client` may be pre-resolved (e.g. the user's imported OAuthClientConfig row);
    when omitted the server-level configuration is used (honest 400 if absent)."""
    if client is None:
        try:
            client = gmail_service.resolve_client_config()
        except gmail_service.GmailOauthError as e:
            # Be honest: no client config -> no authorization URL (never fabricate one)
            raise HTTPException(400, f"Google OAuth is not configured: {e}")
    state = gmail_service.generate_state()
    if _oauth_redis is not None:
        _oauth_redis.setex(f"oauthstate:{state}", 600, json.dumps({"user_id": user.id}))
    else:
        _pending_oauth_states[state] = {"user_id": user.id}
    auth_url = gmail_service.build_authorization_url(client, state)
    return {"authorization_url": auth_url}


_pending_oauth_states: dict = {}


@router.get("/google/start")
async def google_start(request: Request, user=Depends(get_current_user)):
    return await google_start_impl(user)


@router.get("/google/callback")
async def google_callback(request: Request, code: str = "", state: str = "", error: str = "",
                          db: AsyncSession = Depends(get_db)):
    """Handle Google's response: verify state, exchange code for tokens, store encrypted."""
    from app.models import GmailAccount, OAuthClientConfig, OAuthToken

    if error:
        return HTMLResponse(f"<h3>Google authorization failed: {error}</h3>"
                            f"<p><a href='{settings.FRONTEND_BASE_URL}/gmail-accounts'>Back</a></p>")
    if not code or not state:
        raise HTTPException(400, "Missing code or state")

    # 1) verify state (CSRF protection of OAuth handshake)
    user_id = None
    if _oauth_redis is not None:
        raw = _oauth_redis.get(f"oauthstate:{state}")
        if not raw:
            raise HTTPException(400, "Invalid or expired OAuth state")
        data = json.loads(raw)
        _oauth_redis.delete(f"oauthstate:{state}")
        user_id = data["user_id"]
    else:
        entry = _pending_oauth_states.pop(state, None)
        if not entry:
            raise HTTPException(400, "Invalid or expired OAuth state")
        user_id = entry["user_id"]

    # 2) resolve client config (latest imported by this user, else server default)
    cfg_row = (await db.execute(
        select(OAuthClientConfig)
        .where(OAuthClientConfig.user_id == user_id, OAuthClientConfig.is_active == True)  # noqa
        .order_by(OAuthClientConfig.id.desc()))).scalars().first()
    client = gmail_service.resolve_client_config(cfg_row)

    # 3) exchange code for tokens (real HTTP call to Google)
    try:
        tokens = gmail_service.exchange_code_for_tokens(client, code)
    except gmail_service.GmailOauthError as e:
        return HTMLResponse(f"<h3>Token exchange failed</h3><pre>{str(e)[:500]}</pre>")

    # 4) identify the Gmail address that authorized
    try:
        userinfo = gmail_service.get_userinfo(tokens["access_token"])
        gmail_email = userinfo.get("email", "").lower()
    except Exception as e:
        raise HTTPException(502, f"Could not fetch userinfo: {e}")

    if not gmail_email:
        raise HTTPException(502, "Google did not return an email address")

    # 5) persist account + encrypted tokens
    account = (await db.execute(select(GmailAccount)
                                .where(GmailAccount.user_id == user_id,
                                       GmailAccount.email == gmail_email))).scalar_one_or_none()
    from datetime import timedelta as _td
    expires_at = datetime.now(timezone.utc) + _td(seconds=int(tokens.get("expires_in", 3600)))
    if account:
        account.status = "authorized"
        account.sending_method = account.sending_method or "api"
        account.token_expiry = expires_at
        if account.oauth_token:
            account.oauth_token.access_token = tokens["access_token"]
            account.oauth_token.refresh_token = tokens.get("refresh_token") or account.oauth_token.refresh_token
            account.oauth_token.expires_at = expires_at
            account.oauth_token.granted_scopes = tokens.get("scope", "")
        else:
            account.oauth_token = OAuthToken(
                access_token=tokens["access_token"],
                refresh_token=tokens.get("refresh_token", ""),
                scope=tokens.get("scope", ""),
                expires_at=expires_at,
                granted_scopes=tokens.get("scope", ""),
            )
    else:
        account = GmailAccount(
            user_id=user_id, email=gmail_email, display_name=userinfo.get("name", ""),
            status="authorized", sending_method="api",
            google_user_id=userinfo.get("sub", ""), token_expiry=expires_at,
            oauth_config_id=cfg_row.id if cfg_row else None,
        )
        account.oauth_token = OAuthToken(
            access_token=tokens["access_token"],
            refresh_token=tokens.get("refresh_token", ""),
            scope=tokens.get("scope", ""),
            expires_at=expires_at,
            granted_scopes=tokens.get("scope", ""),
        )
        db.add(account)
    await db.commit()

    await log_audit(db, user_id, "gmail.authorized", "gmail_account", account.id,
                    {"email": gmail_email}, request.client.host)

    redirect = f"{settings.FRONTEND_BASE_URL}/gmail-accounts?connected={gmail_email}"
    return RedirectResponse(redirect, status_code=303)
