import type { NextResponse } from "next/server"
import { decodeJwtPayload } from "@/lib/auth/jwt-claims"
import { oidcServerFetchHeaders } from "@/lib/auth/oidc-config"

/** Ultimail session lifetime — independent of short-lived OIDC access tokens. */
export const SESSION_MAX_AGE_SEC = 60 * 60 * 24 * 365

export const SESSION_COOKIE_NAMES = {
  session: "ulti_session",
  accessToken: "ulti_access_token",
  refreshToken: "ulti_refresh_token",
  expiresAt: "ulti_expires_at",
} as const

export type TokenResponse = {
  access_token: string
  refresh_token?: string
  expires_in?: number
  id_token?: string
  token_type?: string
}

function sessionCookieSecure(): boolean {
  if (process.env.COOKIE_SECURE === "true") return true
  if (process.env.COOKIE_SECURE === "false") return false
  const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? ""
  try {
    return new URL(appUrl).protocol === "https:"
  } catch {
    return false
  }
}

export function sessionCookieOptions() {
  return {
    httpOnly: true,
    sameSite: "lax" as const,
    path: "/",
    maxAge: SESSION_MAX_AGE_SEC,
    secure: sessionCookieSecure(),
  }
}

export function computeExpiresAt(expiresIn: number): number {
  return Date.now() + expiresIn * 1000
}

export function isIdTokenJwtValid(accessToken: string | undefined): boolean {
  if (!accessToken) return false
  const claims = decodeJwtPayload(accessToken)
  const exp = claims?.exp
  if (typeof exp !== "number") return false
  return Date.now() < exp * 1000
}

export function isAccessTokenValid(
  accessToken: string | undefined,
  expiresAtRaw: string | undefined
): boolean {
  if (!accessToken) return false
  const expiresAt = Number(expiresAtRaw)
  if (Number.isFinite(expiresAt) && Date.now() < expiresAt) return true
  return isIdTokenJwtValid(accessToken)
}

type OidcTokenConfig = {
  tokenEndpoint: string
  clientId: string
  clientSecret: string
}

export async function exchangeRefreshToken(
  refreshToken: string,
  cfg: OidcTokenConfig
): Promise<TokenResponse> {
  const body = new URLSearchParams({
    grant_type: "refresh_token",
    client_id: cfg.clientId,
    client_secret: cfg.clientSecret,
    refresh_token: refreshToken,
  })
  const res = await fetch(cfg.tokenEndpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      ...oidcServerFetchHeaders(),
    },
    body,
  })
  if (!res.ok) {
    throw new Error(`refresh_failed:${res.status}`)
  }
  return (await res.json()) as TokenResponse
}

export function resolveBearerToken(tokens: TokenResponse): string {
  if (!tokens.id_token) {
    throw new Error("no_id_token_in_response")
  }
  return tokens.id_token
}

export function applySessionCookies(
  response: NextResponse,
  tokens: TokenResponse,
  bearer?: string
) {
  const token = bearer ?? resolveBearerToken(tokens)
  const expiresIn = tokens.expires_in ?? 3600
  const opts = sessionCookieOptions()

  response.cookies.set(SESSION_COOKIE_NAMES.session, "1", opts)
  response.cookies.set(SESSION_COOKIE_NAMES.accessToken, token, opts)
  if (tokens.refresh_token) {
    response.cookies.set(
      SESSION_COOKIE_NAMES.refreshToken,
      tokens.refresh_token,
      opts
    )
  }
  response.cookies.set(
    SESSION_COOKIE_NAMES.expiresAt,
    String(computeExpiresAt(expiresIn)),
    opts
  )
}
