import crypto from 'node:crypto'

export interface DesktopLinkRecord {
  userId: string
  email: string
  plan: string
  role: string
  canDownload: boolean
  accessStatus: string
  createdAt: number
}

interface DesktopLinkRateLimitBucket {
  count: number
  resetAt: number
}

interface DesktopLinkStoreOptions {
  ttlMs: number
  rateLimitMax: number
  rateLimitWindowMs: number
  now?: () => number
  randomBytes?: (size: number) => Buffer
}

interface DesktopLinkIssueInput {
  rateLimitKey: string
  userId: string
  email: string
  plan: string
  role: string
  canDownload: boolean
  accessStatus: string
}

type DesktopLinkIssueResult =
  | { ok: true; token: string; expiresAt: string }
  | { ok: false; error: 'rate_limited' }

function normalizeOriginCandidate(candidate: string): string | null {
  const trimmed = candidate.trim()
  if (!trimmed) {
    return null
  }

  try {
    return new URL(trimmed).origin.toLowerCase()
  } catch {
    return null
  }
}

export function buildAllowedOriginMatcher(websiteDomain: string) {
  const allowedOrigin = normalizeOriginCandidate(websiteDomain)
  if (!allowedOrigin) {
    throw new Error(`WEBSITE_DOMAIN '${websiteDomain}' must be an absolute origin.`)
  }

  return (candidate: string): boolean => normalizeOriginCandidate(candidate) === allowedOrigin
}

export function createDesktopLinkStore({
  ttlMs,
  rateLimitMax,
  rateLimitWindowMs,
  now = () => Date.now(),
  randomBytes = crypto.randomBytes,
}: DesktopLinkStoreOptions) {
  const desktopLinkTokens = new Map<string, DesktopLinkRecord>()
  const rateLimitBuckets = new Map<string, DesktopLinkRateLimitBucket>()

  function cleanupExpiredTokens() {
    const cutoff = now() - ttlMs
    for (const [token, record] of desktopLinkTokens) {
      if (record.createdAt < cutoff) {
        desktopLinkTokens.delete(token)
      }
    }

    for (const [key, bucket] of rateLimitBuckets) {
      if (bucket.resetAt <= now()) {
        rateLimitBuckets.delete(key)
      }
    }
  }

  function isRateLimited(rateLimitKey: string) {
    const currentTime = now()
    const bucket = rateLimitBuckets.get(rateLimitKey)
    if (!bucket || currentTime >= bucket.resetAt) {
      rateLimitBuckets.set(rateLimitKey, {
        count: 1,
        resetAt: currentTime + rateLimitWindowMs,
      })
      return false
    }

    bucket.count += 1
    return bucket.count > rateLimitMax
  }

  function issueToken({
    rateLimitKey,
    userId,
    email,
    plan,
    role,
    canDownload,
    accessStatus,
  }: DesktopLinkIssueInput): DesktopLinkIssueResult {
    cleanupExpiredTokens()
    if (isRateLimited(rateLimitKey)) {
      return { ok: false, error: 'rate_limited' }
    }

    const createdAt = now()
    const token = randomBytes(32).toString('hex')
    desktopLinkTokens.set(token, {
      userId,
      email,
      plan,
      role,
      canDownload,
      accessStatus,
      createdAt,
    })

    return {
      ok: true,
      token,
      expiresAt: new Date(createdAt + ttlMs).toISOString(),
    }
  }

  function verifyAndConsumeToken(token: string): DesktopLinkRecord | null {
    cleanupExpiredTokens()

    const normalizedToken = token.trim()
    if (!/^[a-f0-9]{64}$/i.test(normalizedToken)) {
      return null
    }

    const record = desktopLinkTokens.get(normalizedToken)
    if (!record) {
      return null
    }

    desktopLinkTokens.delete(normalizedToken)
    return record
  }

  return {
    cleanupExpiredTokens,
    issueToken,
    verifyAndConsumeToken,
    getTokenCount: () => desktopLinkTokens.size,
  }
}
