"""Unified Google OAuth token management for Calendar + Gmail."""

import asyncio
import json
import os
import time
from pathlib import Path

import httpx

TOKENS_PATH = Path.home() / ".fliiq" / "google_tokens.json"
_OLD_TOKENS_PATH = Path.home() / ".fliiq" / "google_calendar_tokens.json"

_refresh_lock = asyncio.Lock()


def load_tokens() -> dict:
    """Load all account tokens from disk. Auto-migrates from old path."""
    if not TOKENS_PATH.exists() and _OLD_TOKENS_PATH.exists():
        _OLD_TOKENS_PATH.rename(TOKENS_PATH)

    if not TOKENS_PATH.exists():
        return {}
    try:
        return json.loads(TOKENS_PATH.read_text())
    except (json.JSONDecodeError, OSError):
        return {}


def save_tokens(tokens: dict) -> None:
    """Write all account tokens back to disk."""
    TOKENS_PATH.parent.mkdir(parents=True, exist_ok=True)
    TOKENS_PATH.write_text(json.dumps(tokens, indent=2))


async def get_access_token(account_email: str | None = None) -> tuple[str, str]:
    """Get a valid (email, access_token) pair. Refreshes if expired.

    Returns: (email, access_token)
    Raises ValueError if no tokens found or refresh fails.
    """
    async with _refresh_lock:
        all_tokens = load_tokens()
        if not all_tokens:
            raise ValueError(
                "No Google accounts authorized. Run: fliiq google auth"
            )

        if account_email:
            if account_email not in all_tokens:
                available = ", ".join(all_tokens.keys())
                raise ValueError(
                    f"No tokens for '{account_email}'. "
                    f"Authorized: {available}. Run: fliiq google auth"
                )
            key = account_email
        else:
            key = next(iter(all_tokens))

        tokens = all_tokens[key]

        if tokens.get("expires_at", 0) < time.time():
            client_id = os.environ.get("GOOGLE_CLIENT_ID")
            client_secret = os.environ.get("GOOGLE_CLIENT_SECRET")
            if not client_id or not client_secret:
                raise ValueError(
                    "Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET. "
                    "Set them in your .env file."
                )
            async with httpx.AsyncClient(timeout=30) as client:
                resp = await client.post(
                    "https://oauth2.googleapis.com/token",
                    data={
                        "client_id": client_id,
                        "client_secret": client_secret,
                        "refresh_token": tokens["refresh_token"],
                        "grant_type": "refresh_token",
                    },
                )
            if resp.status_code != 200:
                raise ValueError(
                    f"Token refresh failed ({resp.status_code}): {resp.text}. "
                    "Run 'fliiq google auth' again."
                )
            new_data = resp.json()
            tokens["access_token"] = new_data["access_token"]
            tokens["expires_at"] = time.time() + new_data.get("expires_in", 3600) - 60
            all_tokens[key] = tokens
            save_tokens(all_tokens)

        return key, tokens["access_token"]


async def resolve_gmail_creds(
    gmail_address: str | None = None,
    gmail_app_password: str | None = None,
) -> tuple[str, str, str]:
    """Resolve Gmail credentials: try OAuth first, fall back to app password.

    Returns: (email_address, auth_method, credential)
      - auth_method "oauth2":      credential is an access_token
      - auth_method "app_password": credential is the app password

    Raises ValueError if neither method is available.
    """
    all_tokens = load_tokens()
    target_email = gmail_address or os.environ.get("FLIIQ_GMAIL_ADDRESS") or os.environ.get("GMAIL_ADDRESS")

    # Try OAuth: specific account requested and exists in tokens
    if target_email and target_email in all_tokens:
        try:
            email, token = await get_access_token(target_email)
            return email, "oauth2", token
        except ValueError:
            pass  # Fall through to app password

    # Try OAuth: no specific address, use first authorized account
    if not gmail_address and all_tokens:
        try:
            email, token = await get_access_token()
            return email, "oauth2", token
        except ValueError:
            pass

    # Fall back to app password
    address = gmail_address or os.environ.get("FLIIQ_GMAIL_ADDRESS") or os.environ.get("GMAIL_ADDRESS")
    if not address:
        raise ValueError(
            "No Gmail credentials available. Either:\n"
            "  1. Run 'fliiq google auth' for OAuth access, or\n"
            "  2. Set FLIIQ_GMAIL_ADDRESS + FLIIQ_GMAIL_APP_PASSWORD in .env"
        )

    password = gmail_app_password or os.environ.get("FLIIQ_GMAIL_APP_PASSWORD") or os.environ.get("GMAIL_APP_PASSWORD")
    if not password:
        raise ValueError(
            f"No credentials for {address}. Either:\n"
            "  1. Run 'fliiq google auth' for OAuth access, or\n"
            "  2. Set FLIIQ_GMAIL_APP_PASSWORD in .env"
        )

    return address, "app_password", password


def get_fliiq_email() -> str | None:
    """Return Fliiq's own bot email from env vars, or None if not configured."""
    return os.environ.get("FLIIQ_GMAIL_ADDRESS") or os.environ.get("GMAIL_ADDRESS") or None
