import { describe, it, expect, vi } from 'vitest'
import { randomUUID } from 'node:crypto'
import type { HostRecord, HostStatus } from 'control-plane/src/model/records.js'
import {
  createStoreRouteResolver,
  type HostSubdomainLookup,
} from '../../src/wiring/route-resolver.js'

const NOW_ISO = '2026-07-06T00:00:00.000Z'

/** Build a full §4.2 HostRecord fixture keyed by its subdomain label. */
function makeHost(subdomain: string, status: HostStatus = 'online'): HostRecord {
  return {
    hostId: randomUUID(),
    accountId: randomUUID(),
    subdomain,
    agentPubkey: new Uint8Array([1, 2, 3]),
    enrollFpr: 'fpr:' + subdomain,
    status,
    lastSeen: NOW_ISO,
    createdAt: NOW_ISO,
    revokedAt: status === 'revoked' ? NOW_ISO : null,
  }
}

/** A minimal HostStore lookup fake: exact-match subdomain → record, else null (matches pg.ts). */
function fakeHosts(records: readonly HostRecord[]): HostSubdomainLookup {
  const bySub = new Map(records.map((r) => [r.subdomain, r]))
  return {
    getBySubdomain: vi.fn(async (subdomain: string) => bySub.get(subdomain) ?? null),
  }
}

describe('createStoreRouteResolver', () => {
  it('resolves a known online host to {hostId, accountId, subdomain}', async () => {
    // Arrange
    const host = makeHost('alice')
    const resolver = createStoreRouteResolver({ hosts: fakeHosts([host]) })

    // Act
    const resolved = await resolver.resolveSubdomain('alice')

    // Assert
    expect(resolved).toEqual({
      hostId: host.hostId,
      accountId: host.accountId,
      subdomain: 'alice',
    })
  })

  it('returns null (fail-closed) for an unknown subdomain', async () => {
    // Arrange
    const resolver = createStoreRouteResolver({ hosts: fakeHosts([makeHost('alice')]) })

    // Act
    const resolved = await resolver.resolveSubdomain('nobody')

    // Assert
    expect(resolved).toBeNull()
  })

  it('fails closed (returns null) for a revoked host even though the row still exists', async () => {
    // Arrange
    const resolver = createStoreRouteResolver({ hosts: fakeHosts([makeHost('alice', 'revoked')]) })

    // Act
    const resolved = await resolver.resolveSubdomain('alice')

    // Assert
    expect(resolved).toBeNull()
  })

  it('resolves offline and draining hosts (only revoked fails closed)', async () => {
    // Arrange
    const offline = makeHost('bob', 'offline')
    const draining = makeHost('carol', 'draining')
    const resolver = createStoreRouteResolver({ hosts: fakeHosts([offline, draining]) })

    // Act + Assert
    expect(await resolver.resolveSubdomain('bob')).toEqual({
      hostId: offline.hostId,
      accountId: offline.accountId,
      subdomain: 'bob',
    })
    expect(await resolver.resolveSubdomain('carol')).toEqual({
      hostId: draining.hostId,
      accountId: draining.accountId,
      subdomain: 'carol',
    })
  })

  it('derives identity only from the store record, not the caller (INV3)', async () => {
    // Arrange: the stored record carries the authoritative accountId/hostId.
    const host = makeHost('alice')
    const resolver = createStoreRouteResolver({ hosts: fakeHosts([host]) })

    // Act
    const resolved = await resolver.resolveSubdomain('alice')

    // Assert: values come from the record, never fabricated from the input label.
    expect(resolved?.accountId).toBe(host.accountId)
    expect(resolved?.hostId).toBe(host.hostId)
  })

  it('passes the exact subdomain label through to getBySubdomain', async () => {
    // Arrange
    const hosts = fakeHosts([makeHost('alice')])
    const resolver = createStoreRouteResolver({ hosts })

    // Act
    await resolver.resolveSubdomain('alice')

    // Assert
    expect(hosts.getBySubdomain).toHaveBeenCalledWith('alice')
    expect(hosts.getBySubdomain).toHaveBeenCalledTimes(1)
  })

  it('propagates store errors (no silent swallow)', async () => {
    // Arrange: a store that throws (e.g. DB unavailable) must NOT be masked as a null resolve.
    const boom = new Error('db down')
    const resolver = createStoreRouteResolver({
      hosts: { getBySubdomain: vi.fn(async () => { throw boom }) },
    })

    // Act + Assert
    await expect(resolver.resolveSubdomain('alice')).rejects.toThrow('db down')
  })
})
