import { describe, expect, it } from 'vitest'
import { isMeteringStorageError, isMeteringValidationError } from './errors.js'
import { resolve } from './resolve.js'
import type { EntitlementPolicy, SubjectRef } from './types.js'

const subject: SubjectRef = { tenantId: 'tenant-a', account: 'account-a' }
const at = new Date('2026-07-15T12:00:00.000Z')

const policy: EntitlementPolicy = {
  meter: 'api.calls',
  includedUnits: 10n,
  overage: 'allow',
  period: {
    id: 'period-a',
    startsAt: new Date('2026-07-01T00:00:00.000Z'),
    endsAt: new Date('2026-08-01T00:00:00.000Z'),
  },
  version: 'policy-a',
}

describe('resolve', () => {
  it('delegates policy resolution and passes through an entitled policy', async () => {
    let received: { subject: SubjectRef; meter: string; at: Date } | undefined
    const policies = {
      resolve: async (receivedSubject: SubjectRef, meter: string, receivedAt: Date) => {
        received = { subject: receivedSubject, meter, at: receivedAt }
        return policy
      },
    }

    await expect(resolve({ policies }, subject, 'api.calls', at)).resolves.toBe(policy)
    expect(received).toEqual({ subject, meter: 'api.calls', at })
  })

  it('passes through null when no policy applies', async () => {
    const policies = { resolve: async () => null }

    await expect(resolve({ policies }, subject, 'api.calls', at)).resolves.toBeNull()
  })

  it('wraps policy-store failures as typed storage errors', async () => {
    const policyFailure = new Error('policy store unavailable')
    const policies = {
      resolve: async () => {
        throw policyFailure
      },
    }

    await expect(resolve({ policies }, subject, 'api.calls', at)).rejects.toSatisfy((error) => {
      return isMeteringStorageError(error) && error.operation === 'resolve' && error.cause === policyFailure
    })
  })

  it('rejects malformed policy quantities and ceilings', async () => {
    const malformedPolicies = [
      { ...policy, includedUnits: -1n },
      { ...policy, includedUnits: 10n, hardLimit: 9n },
    ]

    for (const malformed of malformedPolicies) {
      await expect(
        resolve({ policies: { resolve: async () => malformed } }, subject, 'api.calls', at),
      ).rejects.toSatisfy(isMeteringValidationError)
    }

    await expect(
      resolve(
        {
          policies: {
            resolve: async () => ({ ...policy, period: undefined } as unknown as EntitlementPolicy),
          },
        },
        subject,
        'api.calls',
        at,
      ),
    ).rejects.toSatisfy(isMeteringValidationError)
  })

  it('rejects policy quantities at or above the module magnitude bound', async () => {
    for (const oversized of [
      { ...policy, includedUnits: 10n ** 21n },
      { ...policy, hardLimit: 10n ** 21n },
    ]) {
      await expect(
        resolve({ policies: { resolve: async () => oversized } }, subject, 'api.calls', at),
      ).rejects.toSatisfy(isMeteringValidationError)
    }
  })

  it('rejects an empty requested meter before consulting policy storage', async () => {
    let calls = 0
    const policies = {
      resolve: async () => {
        calls += 1
        return policy
      },
    }

    await expect(resolve({ policies }, subject, '   ', at)).rejects.toSatisfy(isMeteringValidationError)
    expect(calls).toBe(0)
  })
})
