interface ServiceAccount {
  client_email: string
  private_key: string
  project_id: string
}

interface CachedToken {
  token: string
  expiresAt: number
}

let serviceAccount: ServiceAccount | null = null
let privateKey: CryptoKey | null = null
let cachedToken: CachedToken | null = null

export function getServiceAccount(): ServiceAccount {
  if (serviceAccount) return serviceAccount

  const credentialsJson = Deno.env.get('GOOGLE_CREDENTIALS_JSON')
  if (!credentialsJson?.trim()) {
    throw new Error('GOOGLE_CREDENTIALS_JSON environment variable is required')
  }

  serviceAccount = JSON.parse(credentialsJson)
  return serviceAccount!
}

async function getPrivateKey(): Promise<CryptoKey> {
  if (privateKey) return privateKey

  const pem = getServiceAccount().private_key
  const pemContents = pem
    .replace(/-----BEGIN PRIVATE KEY-----/, '')
    .replace(/-----END PRIVATE KEY-----/, '')
    .replace(/\s/g, '')

  const der = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0))

  privateKey = await crypto.subtle.importKey(
    'pkcs8',
    der.buffer,
    { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    false,
    ['sign']
  )

  return privateKey
}

function b64url(data: ArrayBuffer | string): string {
  const str = typeof data === 'string'
    ? btoa(data)
    : btoa(String.fromCharCode(...new Uint8Array(data)))
  return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

export async function getAccessToken(): Promise<string> {
  const now = Date.now()

  if (cachedToken && cachedToken.expiresAt > now + 60_000) {
    return cachedToken.token
  }

  const sa = getServiceAccount()
  const iat = Math.floor(now / 1000)
  const exp = iat + 3600

  const header = b64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }))
  const payload = b64url(JSON.stringify({
    iss: sa.client_email,
    scope: 'https://www.googleapis.com/auth/firebase.messaging',
    aud: 'https://oauth2.googleapis.com/token',
    exp,
    iat
  }))

  const signingInput = `${header}.${payload}`
  const key = await getPrivateKey()
  const signature = await crypto.subtle.sign(
    'RSASSA-PKCS1-v1_5',
    key,
    new TextEncoder().encode(signingInput)
  )

  const jwt = `${signingInput}.${b64url(signature)}`

  const res = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion: jwt
    })
  })

  if (!res.ok) {
    throw new Error(`Failed to get FCM access token: ${await res.text()}`)
  }

  const { access_token, expires_in } = await res.json()
  cachedToken = { token: access_token, expiresAt: now + expires_in * 1000 }

  return access_token
}
