import type { SubjectRef } from './types.js'

export class EntitlementDeniedError extends Error {
  override readonly name = 'EntitlementDeniedError'
  readonly code = 'ENTITLEMENT_DENIED' as const

  constructor(
    readonly context: {
      subject: SubjectRef
      meter: string
      at: Date
    },
  ) {
    super(`entitlement denied: ${context.subject.tenantId}/${context.subject.account}/${context.meter}`)
  }
}

export class QuotaExhaustedError extends Error {
  override readonly name = 'QuotaExhaustedError'
  readonly code = 'QUOTA_EXHAUSTED' as const
  readonly requested: bigint
  readonly includedRemaining: bigint
  readonly capacityRemaining: bigint | null

  constructor(
    readonly context: {
      requested: bigint
      includedRemaining: bigint
      capacityRemaining: bigint | null
    },
  ) {
    super(`quota exhausted: requested ${context.requested}, capacity ${context.capacityRemaining ?? 'unlimited'}`)
    this.requested = context.requested
    this.includedRemaining = context.includedRemaining
    this.capacityRemaining = context.capacityRemaining
  }
}

export class ReservationConflictError extends Error {
  override readonly name = 'ReservationConflictError'
  readonly code = 'RESERVATION_CONFLICT' as const

  constructor(readonly context: { reservationId: string; detail: string }) {
    super(`reservation conflict: ${context.reservationId} — ${context.detail}`)
  }
}

export class ReservationNotFoundError extends Error {
  override readonly name = 'ReservationNotFoundError'
  readonly code = 'RESERVATION_NOT_FOUND' as const

  constructor(readonly reservationId: string) {
    super(`reservation not found: ${reservationId}`)
  }
}

export class ReservationExpiredError extends Error {
  override readonly name = 'ReservationExpiredError'
  readonly code = 'RESERVATION_EXPIRED' as const

  constructor(readonly reservationId: string) {
    super(`reservation expired: ${reservationId}`)
  }
}

export class IdempotencyConflictError extends Error {
  override readonly name = 'IdempotencyConflictError'
  readonly code = 'IDEMPOTENCY_CONFLICT' as const

  constructor(readonly context: { tenantId: string; idempotencyKey: string }) {
    super(`idempotency conflict: ${context.tenantId}/${context.idempotencyKey}`)
  }
}

export class MeteringValidationError extends Error {
  override readonly name = 'MeteringValidationError'
  readonly code = 'METERING_VALIDATION' as const

  constructor(
    readonly field: string,
    readonly detail: string,
  ) {
    super(`metering validation failed: ${field} — ${detail}`)
  }
}

export class MeteringStorageError extends Error {
  override readonly name = 'MeteringStorageError'
  readonly code = 'METERING_STORAGE' as const

  constructor(
    readonly operation: string,
    readonly cause: unknown,
  ) {
    super(`metering storage failed: ${operation}`)
  }
}

export type MeteringError =
  | EntitlementDeniedError
  | QuotaExhaustedError
  | ReservationConflictError
  | ReservationNotFoundError
  | ReservationExpiredError
  | IdempotencyConflictError
  | MeteringValidationError
  | MeteringStorageError

type ErrorTag = { name?: unknown; code?: unknown }

function hasErrorTag(error: unknown, name: string, code: string): boolean {
  if (typeof error !== 'object' || error === null) return false
  const tag = error as ErrorTag
  return tag.name === name && tag.code === code
}

export function isEntitlementDeniedError(error: unknown): error is EntitlementDeniedError {
  return hasErrorTag(error, 'EntitlementDeniedError', 'ENTITLEMENT_DENIED')
}

export function isQuotaExhaustedError(error: unknown): error is QuotaExhaustedError {
  return hasErrorTag(error, 'QuotaExhaustedError', 'QUOTA_EXHAUSTED')
}

export function isReservationConflictError(error: unknown): error is ReservationConflictError {
  return hasErrorTag(error, 'ReservationConflictError', 'RESERVATION_CONFLICT')
}

export function isReservationNotFoundError(error: unknown): error is ReservationNotFoundError {
  return hasErrorTag(error, 'ReservationNotFoundError', 'RESERVATION_NOT_FOUND')
}

export function isReservationExpiredError(error: unknown): error is ReservationExpiredError {
  return hasErrorTag(error, 'ReservationExpiredError', 'RESERVATION_EXPIRED')
}

export function isIdempotencyConflictError(error: unknown): error is IdempotencyConflictError {
  return hasErrorTag(error, 'IdempotencyConflictError', 'IDEMPOTENCY_CONFLICT')
}

export function isMeteringValidationError(error: unknown): error is MeteringValidationError {
  return hasErrorTag(error, 'MeteringValidationError', 'METERING_VALIDATION')
}

export function isMeteringStorageError(error: unknown): error is MeteringStorageError {
  return hasErrorTag(error, 'MeteringStorageError', 'METERING_STORAGE')
}

export function isMeteringError(error: unknown): error is MeteringError {
  return (
    isEntitlementDeniedError(error) ||
    isQuotaExhaustedError(error) ||
    isReservationConflictError(error) ||
    isReservationNotFoundError(error) ||
    isReservationExpiredError(error) ||
    isIdempotencyConflictError(error) ||
    isMeteringValidationError(error) ||
    isMeteringStorageError(error)
  )
}
