import { and, eq } from "drizzle-orm"
import { db } from "@/lib/db"
import { esign_connections } from "@/lib/db/schema"
import { encrypt, decrypt } from "@/lib/crypto"
import { getAdapter } from "./registry"
import type { ESignCredentials, ESignProvider, ESignTokens } from "./types"

// Per-owner e-sign connection storage + credential resolution. Mirrors
// lib/accounting/index.ts: tokens are AES-256-GCM encrypted at rest, decrypted
// on demand, and DocuSign access tokens are transparently refreshed near expiry.

/** Upsert an encrypted connection for (owner, provider). */
export async function saveEsignConnection(ownerId: string, provider: ESignProvider, tokens: ESignTokens) {
  const values = {
    user_id: ownerId,
    provider,
    access_token: encrypt(tokens.accessToken),
    refresh_token: tokens.refreshToken ? encrypt(tokens.refreshToken) : null,
    expires_at: tokens.expiresAt,
    account_id: tokens.accountId,
    base_uri: tokens.baseUri,
    account_name: tokens.accountName,
    status: "active" as const,
    last_error: null,
  }
  const existing = await db.query.esign_connections.findFirst({
    where: and(eq(esign_connections.user_id, ownerId), eq(esign_connections.provider, provider)),
    columns: { id: true },
  })
  if (existing) {
    await db
      .update(esign_connections)
      .set({ ...values, updated_at: new Date().toISOString() })
      .where(eq(esign_connections.id, existing.id))
  } else {
    await db.insert(esign_connections).values(values)
  }
}

export async function getEsignConnection(ownerId: string, provider: ESignProvider) {
  return db.query.esign_connections.findFirst({
    where: and(eq(esign_connections.user_id, ownerId), eq(esign_connections.provider, provider)),
  })
}

/** Owner-facing list — never leaks tokens. */
export async function listEsignConnections(ownerId: string) {
  const rows = await db.query.esign_connections.findMany({ where: eq(esign_connections.user_id, ownerId) })
  return rows.map((r) => ({
    provider: r.provider,
    accountName: r.account_name,
    status: r.status,
    lastError: r.last_error,
  }))
}

export async function disconnectEsign(ownerId: string, provider: ESignProvider) {
  await db
    .delete(esign_connections)
    .where(and(eq(esign_connections.user_id, ownerId), eq(esign_connections.provider, provider)))
}

/**
 * Resolve ready-to-use credentials for a connected account, refreshing the
 * DocuSign access token first if it's near expiry. Returns null when the owner
 * hasn't connected this provider.
 */
export async function resolveEsignCreds(ownerId: string, provider: ESignProvider): Promise<ESignCredentials | null> {
  const conn = await getEsignConnection(ownerId, provider)
  if (!conn || conn.status === "revoked") return null

  let accessToken = decrypt(conn.access_token)
  const refreshToken = conn.refresh_token ? decrypt(conn.refresh_token) : null
  let accountId = conn.account_id
  let baseUri = conn.base_uri

  const nearExpiry = conn.expires_at && new Date(conn.expires_at).getTime() - Date.now() < 5 * 60_000
  if (nearExpiry && refreshToken) {
    const adapter = getAdapter(provider)
    if (adapter) {
      const next = await adapter.refresh(refreshToken)
      // Account id / base uri are stable across refresh — keep the stored ones.
      await saveEsignConnection(ownerId, provider, {
        ...next,
        accountId: conn.account_id,
        baseUri: conn.base_uri,
        accountName: conn.account_name,
      })
      accessToken = next.accessToken
      accountId = conn.account_id
      baseUri = conn.base_uri
    }
  }

  return { provider, accessToken, refreshToken, accountId, baseUri }
}

/** Flag a connection as errored (e.g. after a failed send/refresh). */
export async function markEsignError(ownerId: string, provider: ESignProvider, message: string) {
  await db
    .update(esign_connections)
    .set({ status: "error", last_error: message.slice(0, 500), updated_at: new Date().toISOString() })
    .where(and(eq(esign_connections.user_id, ownerId), eq(esign_connections.provider, provider)))
}
