import crypto from "crypto"
import type { ESignAdapter, ESignTokens, SendParams } from "./types"

// Dropbox Sign (formerly HelloSign). Per-landlord API-key auth — each landlord
// pastes their own API key (no platform app credentials needed). Docs:
// https://developers.hellosign.com/api/reference/
const BASE = "https://api.hellosign.com/v3"
const TEST_MODE = process.env.DROPBOX_SIGN_TEST_MODE === "true" ? "1" : "0"

function authFor(apiKey: string) {
  return "Basic " + Buffer.from(`${apiKey}:`).toString("base64")
}

/** Verify the `event_hash` (hex HMAC-SHA256 of event_time+event_type, key = API key). */
function verifyEventHash(apiKey: string, ev?: { event_type?: string; event_time?: string; event_hash?: string }): boolean {
  if (!apiKey || !ev?.event_type || !ev?.event_time || !ev?.event_hash) return false
  const expected = crypto.createHmac("sha256", apiKey).update(ev.event_time + ev.event_type).digest("hex")
  const a = Buffer.from(ev.event_hash)
  const b = Buffer.from(expected)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

type DbxEvent = {
  event?: { event_type?: string; event_time?: string; event_hash?: string }
  signature_request?: { signature_request_id?: string }
}

function parseBody(body: string): DbxEvent | null {
  try {
    // Dropbox Sign posts multipart form-data with a `json` field (or raw JSON).
    const m = body.match(/name="json"\r?\n\r?\n([\s\S]*?)\r?\n--/) ?? body.match(/^(\{[\s\S]*\})\s*$/)
    return JSON.parse(m ? m[1] : body)
  } catch {
    return null
  }
}

export const dropboxSign: ESignAdapter = {
  id: "dropbox_sign",
  label: "Dropbox Sign",
  kind: "apikey",
  available: () => true, // landlord brings their own key; no operator setup required

  getAuthUrl(): string {
    throw new Error("Dropbox Sign connects with an API key, not OAuth")
  },
  async exchangeCode(): Promise<ESignTokens> {
    throw new Error("Dropbox Sign connects with an API key, not OAuth")
  },
  async refresh(): Promise<ESignTokens> {
    throw new Error("Dropbox Sign API keys don't expire")
  },

  async connectApiKey(apiKey): Promise<ESignTokens> {
    const key = apiKey.trim()
    if (!key) throw new Error("Enter your Dropbox Sign API key")
    const res = await fetch(`${BASE}/account`, { headers: { Authorization: authFor(key) } })
    if (res.status === 401 || res.status === 403) throw new Error("That API key was rejected by Dropbox Sign")
    if (!res.ok) throw new Error(`Dropbox Sign ${res.status}: could not validate the API key`)
    const j = (await res.json()) as { account?: { email_address?: string } }
    return {
      accessToken: key,
      refreshToken: null,
      expiresAt: null,
      accountId: null,
      baseUri: null,
      accountName: j.account?.email_address ?? "Dropbox Sign account",
    }
  },

  async send(creds, p: SendParams) {
    const fd = new FormData()
    fd.append("subject", p.subject)
    fd.append("message", p.message)
    fd.append("test_mode", TEST_MODE)
    fd.append("signers[0][email_address]", p.signerEmail)
    fd.append("signers[0][name]", p.signerName)
    fd.append("file[0]", new Blob([new Uint8Array(p.document)], { type: "application/pdf" }), p.documentName)

    const res = await fetch(`${BASE}/signature_request/send`, { method: "POST", headers: { Authorization: authFor(creds.accessToken) }, body: fd })
    if (!res.ok) throw new Error(`Dropbox Sign ${res.status}: ${(await res.text()).slice(0, 300)}`)
    const j = (await res.json()) as { signature_request?: { signature_request_id?: string } }
    const id = j.signature_request?.signature_request_id
    if (!id) throw new Error("Dropbox Sign did not return a signature_request_id")
    return { externalId: id }
  },

  peekExternalId(body): string | null {
    return parseBody(body)?.signature_request?.signature_request_id ?? null
  },

  async verifyAndGetStatus(creds, _externalId, body): Promise<"signed" | "declined" | "voided" | null> {
    const ev = parseBody(body)
    if (!ev || !verifyEventHash(creds.accessToken, ev.event)) return null
    switch (ev.event?.event_type) {
      case "signature_request_all_signed":
        return "signed"
      case "signature_request_declined":
        return "declined"
      case "signature_request_canceled":
        return "voided"
      default:
        return null
    }
  },

  async getSignedDocument(creds, externalId): Promise<Buffer | null> {
    const res = await fetch(`${BASE}/signature_request/files/${externalId}?file_type=pdf`, {
      headers: { Authorization: authFor(creds.accessToken) },
    })
    if (!res.ok) return null
    return Buffer.from(await res.arrayBuffer())
  },
}
