from __future__ import annotations

from datetime import datetime, timedelta
import json
import logging
import secrets
from typing import Any, Dict, Optional
from urllib.parse import urlencode

import httpx
import jwt
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession

from ... import settings
from ...api.progress import get_redis
from ...auth_security import require_account
from ...db import get_session
from ...models import IntegrationStatus
from ...security.crypto import decrypt_str
from ...services.google_sheets_oauth import (
    GOOGLE_SHEETS_SCOPE,
    ensure_google_access_token,
    get_google_sheets_client_id,
    get_google_sheets_client_secret,
    get_google_sheets_integration,
    revoke_google_token,
    upsert_google_sheets_integration,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/oauth/google", tags=["Google OAuth"])

NONCE_TTL_SECONDS = 600
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"


def _state_secret() -> str:
    return settings.ENCRYPTION_KEY or settings.JWT_SECRET_KEY or "dev-secret"


def _encode_state(nonce: str) -> str:
    payload = {
        "nonce": nonce,
        "exp": int((datetime.utcnow() + timedelta(seconds=NONCE_TTL_SECONDS)).timestamp()),
    }
    return jwt.encode(payload, _state_secret(), algorithm="HS256")


def _decode_state(state_token: str) -> Dict[str, Any]:
    return jwt.decode(state_token, _state_secret(), algorithms=["HS256"])


def _html_page(title: str, message: str, is_error: bool = False) -> HTMLResponse:
    status_class = "error" if is_error else "success"
    html = f"""
    <html>
      <head>
        <title>{title}</title>
        <style>
          body {{ font-family: Arial, sans-serif; background: #f6f7fb; color: #111; }}
          .card {{ max-width: 520px; margin: 10vh auto; background: #fff; padding: 32px;
            border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); }}
          .{status_class} {{ color: {"#b00020" if is_error else "#0f8a3b"}; font-weight: 700; }}
          .msg {{ margin-top: 12px; line-height: 1.5; }}
        </style>
      </head>
      <body>
        <div class="card">
          <div class="{status_class}">{title}</div>
          <div class="msg">{message}</div>
          <div class="msg">You can close this window.</div>
        </div>
        <script>
          setTimeout(function() {{
            try {{ window.close(); }} catch (e) {{}}
          }}, 1500);
        </script>
      </body>
    </html>
    """
    return HTMLResponse(content=html, status_code=200)


def _error_html(message: str) -> HTMLResponse:
    return _html_page("Google OAuth Failed", message, is_error=True)


def _success_html(message: str) -> HTMLResponse:
    return _html_page("Google Connected", message, is_error=False)


def _google_status_payload(
    *,
    connected: bool,
    state: str,
    message: str,
    expires_in_seconds: Optional[int] = None,
    scopes: Optional[list[str]] = None,
    user_email: Optional[str] = None,
) -> Dict[str, Any]:
    payload: Dict[str, Any] = {
        "connected": connected,
        "state": state,
        "message": message,
        "scopes": scopes or [],
        "user_email": user_email,
    }
    if expires_in_seconds is not None:
        payload["expires_in_seconds"] = expires_in_seconds
    return payload


def _requires_google_reconnect(exc: BaseException) -> bool:
    text = str(exc).lower()
    if "google token refresh failed (" in text and any(
        f"({status})" in text for status in ("400", "401")
    ):
        return True
    return any(
        marker in text
        for marker in (
            "invalid_grant",
            "invalid refresh token",
            "token has been expired or revoked",
            "refresh token missing",
            "missing access_token",
            "revoked",
            "reauth",
            "invalid_request",
            "unauthorized_client",
        )
    )


def _get_redirect_uri(request: Request) -> str:
    if settings.GOOGLE_SHEETS_OAUTH_REDIRECT_URI:
        return settings.GOOGLE_SHEETS_OAUTH_REDIRECT_URI
    if settings.GOOGLE_OAUTH_REDIRECT_URI:
        return settings.GOOGLE_OAUTH_REDIRECT_URI
    base = settings.PUBLIC_BASE_URL or str(request.base_url).rstrip("/")
    return f"{base}/api/v1/oauth/google/callback"


@router.get("/authorize")
async def start_google_connect(
    request: Request,
    account_id: str = Depends(require_account),
    redis=Depends(get_redis),
):
    client_id = get_google_sheets_client_id()
    client_secret = get_google_sheets_client_secret()
    if not client_id or not client_secret:
        raise HTTPException(503, "Google OAuth not configured")

    nonce = secrets.token_urlsafe(32)
    await redis.setex(
        f"google_oauth_nonce:{nonce}",
        NONCE_TTL_SECONDS,
        json.dumps({"account_id": str(account_id)}),
    )

    redirect_uri = _get_redirect_uri(request)
    state_token = _encode_state(nonce)
    params = {
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "response_type": "code",
        "scope": GOOGLE_SHEETS_SCOPE,
        "access_type": "offline",
        "prompt": "consent",
        "state": state_token,
    }
    auth_url = f"{GOOGLE_AUTH_URL}?{urlencode(params)}"
    return {"auth_url": auth_url}


@router.get("/callback")
async def google_callback(
    request: Request,
    code: Optional[str] = Query(None),
    state: Optional[str] = Query(None),
    error: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_session),
    redis=Depends(get_redis),
):
    if error:
        return _error_html(f"Google OAuth error: {error}")
    if not code or not state:
        return _error_html("Missing OAuth code or state")

    try:
        decoded = _decode_state(state)
    except Exception as exc:
        logger.warning("Google OAuth state decode failed: %s", exc)
        return _error_html("Invalid OAuth state")

    nonce = decoded.get("nonce")
    if not nonce:
        return _error_html("Invalid OAuth state (missing nonce)")

    raw = await redis.get(f"google_oauth_nonce:{nonce}")
    if not raw:
        return _error_html("OAuth session expired. Please try again.")
    try:
        payload = json.loads(raw if isinstance(raw, str) else raw.decode("utf-8"))
    except Exception:
        return _error_html("Invalid OAuth session data")

    account_id = payload.get("account_id")
    if not account_id:
        return _error_html("OAuth session missing account context")

    redirect_uri = _get_redirect_uri(request)
    client_id = get_google_sheets_client_id()
    client_secret = get_google_sheets_client_secret()
    try:
        async with httpx.AsyncClient(timeout=20.0) as client:
            resp = await client.post(
                GOOGLE_TOKEN_URL,
                data={
                    "code": code,
                    "client_id": client_id,
                    "client_secret": client_secret,
                    "redirect_uri": redirect_uri,
                    "grant_type": "authorization_code",
                },
            )
        if not resp.is_success:
            return _error_html(
                f"Token exchange failed ({resp.status_code}). Please retry."
            )
        data = resp.json()
    except Exception as exc:
        logger.warning("Google token exchange failed: %s", exc)
        return _error_html("Token exchange failed. Please retry.")

    access_token = data.get("access_token")
    refresh_token = data.get("refresh_token")
    expires_in = data.get("expires_in")
    scope_raw = data.get("scope") or ""
    scopes = [s for s in scope_raw.split(" ") if s]

    if not access_token:
        return _error_html("Google OAuth response missing access token")

    await upsert_google_sheets_integration(
        account_id=account_id,
        access_token=access_token,
        refresh_token=refresh_token,
        expires_in=expires_in,
        scopes=scopes,
        user_email=None,
        db=db,
    )
    return _success_html("Your Google Sheets connection is ready.")


@router.get("/status")
async def google_status(
    account_id: str = Depends(require_account),
    db: AsyncSession = Depends(get_session),
):
    integration = await get_google_sheets_integration(account_id, db)
    if not integration or integration.status == IntegrationStatus.DISCONNECTED:
        return _google_status_payload(
            connected=False,
            state="not_connected",
            message="Connect Google Sheets to enable writes to Sheets.",
        )

    if integration.status == IntegrationStatus.ERROR:
        return _google_status_payload(
            connected=False,
            state="needs_reconnect",
            message="Reconnect Google Sheets to resume writes to Sheets.",
            scopes=integration.scopes or [],
            user_email=integration.user_email,
        )

    try:
        access_token, integration = await ensure_google_access_token(account_id, db)
    except Exception as exc:
        if _requires_google_reconnect(exc) and integration:
            integration.status = IntegrationStatus.ERROR
            integration.updated_at = datetime.utcnow()
            await db.commit()
            return _google_status_payload(
                connected=False,
                state="needs_reconnect",
                message="Reconnect Google Sheets to resume writes to Sheets.",
                scopes=integration.scopes or [],
                user_email=integration.user_email,
            )
        logger.warning("Google Sheets status check failed for %s: %s", account_id, exc)
        return _google_status_payload(
            connected=False,
            state="temporary_error",
            message="Google Sheets status check is temporarily unavailable. Try Refresh.",
            scopes=integration.scopes or [],
            user_email=integration.user_email,
        )

    if not access_token:
        return _google_status_payload(
            connected=False,
            state="needs_reconnect",
            message="Reconnect Google Sheets to resume writes to Sheets.",
            scopes=integration.scopes or [],
            user_email=integration.user_email,
        )

    expires_in_seconds = None
    if integration.token_expires_at:
        expires_in_seconds = int(
            (integration.token_expires_at - datetime.utcnow()).total_seconds()
        )
    return _google_status_payload(
        connected=True,
        state="connected",
        message="Google Sheets is connected for background writes.",
        expires_in_seconds=expires_in_seconds,
        scopes=integration.scopes or [],
        user_email=integration.user_email,
    )


@router.post("/disconnect")
async def google_disconnect(
    account_id: str = Depends(require_account),
    db: AsyncSession = Depends(get_session),
):
    integration = await get_google_sheets_integration(account_id, db)
    if not integration:
        return {"ok": True}

    token = integration.access_token
    if token and settings.ENCRYPTION_KEY:
        try:
            token = decrypt_str(token)
        except Exception as exc:
            logger.warning(
                "Skipping Google token revoke for %s; stored token could not be decrypted: %s",
                account_id,
                exc,
            )
            token = None
    if token:
        try:
            await revoke_google_token(token)
        except Exception:
            pass

    integration.status = IntegrationStatus.DISCONNECTED
    integration.access_token = None
    integration.refresh_token = None
    integration.token_expires_at = None
    integration.updated_at = datetime.utcnow()
    await db.commit()
    return {"ok": True}
