GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_REVOKE_URL = "https://oauth2.googleapis.com/revoke"
GOOGLE_USERINFO_URL = "https://openidconnect.googleapis.com/v1/userinfo"


class GmailOauthError(Exception):
    pass


# ---------------------------------------------------------------------------
# OAuth client configuration resolution
# ---------------------------------------------------------------------------
def resolve_client_config(config=None) -> dict:
    """Return {client_id, client_secret, redirect_uri} for OAuth flows.

    Priority: explicit config dict (per-user imported config) > DB row > env file path > error.
    """
    if config and config.client_id:
        return {
            "client_id": config.client_id,
            "client_secret": config.client_secret,  # decrypted property
            "redirect_uri": config.redirect_uri or f"{settings.BASE_URL}/auth/google/callback",
        }
    path = settings.GOOGLE_OAUTH_CLIENT_JSON_PATH
    if path and __import__("os").path.exists(path):
        with open(path) as f:
            data = json.load(f)
        inst = data.get("installed") or data.get("web")
        if not inst:
            raise GmailOauthError("Invalid Google OAuth client JSON: no 'installed' or 'web' key")
        return {
            "client_id": inst["client_id"],
            "client_secret": inst["client_secret"],
            "redirect_uri": f"{settings.BASE_URL}/auth/google/callback",
        }
    raise GmailOauthError(
        "No Google OAuth client configuration available. Import a client_secret.json in "
        "Gmail Accounts → Import Gmail Configuration, or set GOOGLE_OAUTH_CLIENT_JSON_PATH."
    )


def parse_client_json(content: bytes) -> dict:
    """Validate an uploaded client_secret JSON. Returns normalized dict; raises on invalid."""
    try:
        data = json.loads(content.decode("utf-8"))
    except Exception as e:
        raise GmailOauthError(f"File is not valid JSON: {e}")
    inst = data.get("installed") or data.get("web")
    if not inst or not inst.get("client_id") or not inst.get("client_secret"):
        raise GmailOauthError("JSON must contain an 'installed' or 'web' object with client_id and client_secret")
    return {
        "project_id": inst.get("project", "") or inst.get("project_id", ""),
        "client_id": inst["client_id"],
        "client_secret": inst["client_secret"],
        "redirect_uris": inst.get("redirect_uris", []),
        "auth_uri": inst.get("auth_uri", ""),
        "token_uri": inst.get("token_uri", ""),
    }


# ---------------------------------------------------------------------------
# Authorization flow
# ---------------------------------------------------------------------------
def generate_state() -> str:
    return secrets.token_urlsafe(32)


def build_authorization_url(client: dict, state: str) -> str:
    params = {
        "client_id": client["client_id"],
        "redirect_uri": client["redirect_uri"],
        "response_type": "code",
        "scope": " ".join(ALL_SCOPES),
        "access_type": "offline",       # needed to obtain a refresh token
        "prompt": "consent",            # ensure refresh token is granted
        "include_granted_scopes": "true",
        "state": state,
    }
    return f"{GOOGLE_AUTH_URL}?{urlencode(params)}"


def exchange_code_for_tokens(client: dict, code: str) -> dict:
    """Exchange authorization code for tokens. Real HTTP call to Google."""
    data = {
        "code": code,
        "client_id": client["client_id"],
        "client_secret": client["client_secret"],
        "redirect_uri": client["redirect_uri"],
        "grant_type": "authorization_code",
    }
    resp = requests.post(GOOGLE_TOKEN_URL, data=data, timeout=20)
    if resp.status_code != 200:
        raise GmailOauthError(f"Token exchange failed: {resp.status_code} {resp.text[:300]}")
    tok = resp.json()
    if "access_token" not in tok:
        raise GmailOauthError("Token response missing access_token")
    return tok


def refresh_access_token(client: dict, refresh_token: str) -> dict:
    data = {
        "client_id": client["client_id"],
        "client_secret": client["client_secret"],
        "refresh_token": refresh_token,
        "grant_type": "refresh_token",
    }
    resp = requests.post(GOOGLE_TOKEN_URL, data=data, timeout=20)
    if resp.status_code != 200:
        raise GmailOauthError(f"Token refresh failed: {resp.status_code} {resp.text[:300]}")
    return resp.json()


def revoke_token(token: str) -> bool:
    """Revoke a token at Google. Returns True if revoked successfully."""
    if not token:
        return False
    try:
        resp = requests.post(GOOGLE_REVOKE_URL, params={"token": token}, timeout=15)
        return resp.status_code == 200
    except Exception:
        return False


def get_userinfo(access_token: str) -> dict:
    resp = requests.get(GOOGLE_USERINFO_URL, headers={"Authorization": f"Bearer {access_token}"}, timeout=15)
    if resp.status_code != 200:
        raise GmailOauthError(f"userinfo failed: {resp.status_code}")
    return resp.json()


# ---------------------------------------------------------------------------
# Gmail client construction
# ---------------------------------------------------------------------------
class GmailClient:
    """Authenticated Gmail client for one account (API or XOAUTH2 SMTP)."""

