from __future__ import annotations

import asyncio
from datetime import datetime, timedelta
from typing import Optional, Tuple

import httpx
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from .. import settings
from ..api.progress import get_redis
from ..models import GoogleSheetsIntegration, IntegrationStatus
from ..security.crypto import decrypt_str, encrypt_str

GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_REVOKE_URL = "https://oauth2.googleapis.com/revoke"
GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets"


def _maybe_encrypt(value: Optional[str]) -> Optional[str]:
    if not value:
        return None
    if settings.ENCRYPTION_KEY:
        return encrypt_str(value)
    return value


def _maybe_decrypt(value: Optional[str]) -> Optional[str]:
    if not value:
        return None
    if settings.ENCRYPTION_KEY:
        return decrypt_str(value)
    return value


async def get_google_sheets_integration(
    account_id: str, db: AsyncSession
) -> Optional[GoogleSheetsIntegration]:
    result = await db.execute(
        select(GoogleSheetsIntegration).where(
            GoogleSheetsIntegration.account_id == str(account_id)
        )
    )
    return result.scalar_one_or_none()


async def upsert_google_sheets_integration(
    account_id: str,
    access_token: str,
    refresh_token: Optional[str],
    expires_in: Optional[int],
    scopes: Optional[list[str]] = None,
    user_email: Optional[str] = None,
    db: AsyncSession = None,
) -> GoogleSheetsIntegration:
    if db is None:
        raise ValueError("db session is required")

    integration = await get_google_sheets_integration(account_id, db)
    if not integration:
        integration = GoogleSheetsIntegration(account_id=str(account_id))
        db.add(integration)

    integration.status = IntegrationStatus.CONNECTED
    integration.access_token = _maybe_encrypt(access_token)
    if refresh_token:
        integration.refresh_token = _maybe_encrypt(refresh_token)
    integration.token_expires_at = (
        datetime.utcnow() + timedelta(seconds=expires_in or 3600)
        if expires_in
        else None
    )
    integration.scopes = scopes or integration.scopes
    integration.user_email = user_email or integration.user_email
    integration.updated_at = datetime.utcnow()
    await db.commit()
    await db.refresh(integration)
    return integration


async def refresh_google_access_token(
    integration: GoogleSheetsIntegration, db: AsyncSession
) -> str:
    refresh_token = _maybe_decrypt(integration.refresh_token)
    if not refresh_token:
        raise RuntimeError("Google refresh token missing")

    redis_conn = await get_redis()
    lock_key = f"google_token_refresh:{integration.account_id}"
    acquired = False

    if redis_conn:
        acquired = await redis_conn.set(lock_key, "1", nx=True, ex=10)
        if not acquired:
            await asyncio.sleep(2)
            await db.refresh(integration)
            token = _maybe_decrypt(integration.access_token)
            if integration.token_expires_at and integration.token_expires_at > datetime.utcnow():
                if token:
                    return token
            raise RuntimeError("Concurrent token refresh in progress")

    try:
        async with httpx.AsyncClient(timeout=20.0) as client:
            resp = await client.post(
                GOOGLE_TOKEN_URL,
                data={
                    "client_id": settings.GOOGLE_CLIENT_ID,
                    "client_secret": settings.GOOGLE_CLIENT_SECRET,
                    "refresh_token": refresh_token,
                    "grant_type": "refresh_token",
                },
            )
        if not resp.is_success:
            raise RuntimeError(
                f"Google token refresh failed ({resp.status_code}): {resp.text[:200]}"
            )
        data = resp.json()
        access_token = data.get("access_token")
        expires_in = int(data.get("expires_in", 3600))
        if not access_token:
            raise RuntimeError("Google refresh response missing access_token")

        integration.access_token = _maybe_encrypt(access_token)
        integration.token_expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
        integration.status = IntegrationStatus.CONNECTED
        integration.updated_at = datetime.utcnow()
        await db.commit()
        await db.refresh(integration)
        return access_token
    finally:
        if redis_conn and acquired:
            try:
                await redis_conn.delete(lock_key)
            except Exception:
                pass


async def revoke_google_token(token: str) -> None:
    if not token:
        return
    async with httpx.AsyncClient(timeout=10.0) as client:
        await client.post(GOOGLE_REVOKE_URL, data={"token": token})


async def ensure_google_access_token(
    account_id: str, db: AsyncSession
) -> Tuple[Optional[str], Optional[GoogleSheetsIntegration]]:
    integration = await get_google_sheets_integration(account_id, db)
    if not integration or integration.status != IntegrationStatus.CONNECTED:
        return None, integration

    access_token = _maybe_decrypt(integration.access_token)
    if not access_token:
        return None, integration

    if integration.token_expires_at:
        refresh_at = integration.token_expires_at - timedelta(seconds=60)
        if refresh_at <= datetime.utcnow():
            access_token = await refresh_google_access_token(integration, db)

    return access_token, integration
