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,
    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 _get_redirect_uri(request: Request) -> str:
    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),
):
    if not settings.GOOGLE_CLIENT_ID or not settings.GOOGLE_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": settings.GOOGLE_CLIENT_ID,
        "redirect_uri": redirect_uri,
        "response_type": "code",
        "scope": GOOGLE_SHEETS_SCOPE,
        "access_type": "offline",
        "prompt": "consent",
        "include_granted_scopes": "true",
        "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)
    try:
        async with httpx.AsyncClient(timeout=20.0) as client:
            resp = await client.post(
                GOOGLE_TOKEN_URL,
                data={
                    "code": code,
                    "client_id": settings.GOOGLE_CLIENT_ID,
                    "client_secret": settings.GOOGLE_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.CONNECTED:
        return {"connected": False}

    expires_in_seconds = None
    if integration.token_expires_at:
        expires_in_seconds = int(
            (integration.token_expires_at - datetime.utcnow()).total_seconds()
        )
    return {
        "connected": True,
        "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:
        token = decrypt_str(token)
    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}
