import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mountDashboard } from '../src/dashboard'
import { ApiError } from '../src/errors'
import type { ApiClient } from '../src/api-client'
import type { HostRecord, HostStatus } from '../src/api-schemas'

function host(subdomain: string, status: HostStatus): HostRecord {
  return {
    hostId: `id-${subdomain}`,
    accountId: 'acc',
    subdomain,
    agentPubkey: new Uint8Array([1]),
    enrollFpr: `fpr-${subdomain}`,
    status,
    lastSeen: '2026-01-01T00:00:00Z',
    createdAt: '2026-01-01T00:00:00Z',
    revokedAt: status === 'revoked' ? '2026-01-02T00:00:00Z' : null,
  }
}

function fakeApi(listHosts: () => Promise<readonly HostRecord[]>): ApiClient {
  return {
    listHosts,
    getHost: vi.fn(),
    requestPairingCode: vi.fn(),
    hostStatus: vi.fn(),
    issueCapabilityToken: vi.fn(),
  } as unknown as ApiClient
}

describe('mountDashboard (T5)', () => {
  let root: HTMLElement
  beforeEach(() => {
    root = document.createElement('div')
    document.body.append(root)
  })

  it('renders one card per host with the correct status class', async () => {
    const api = fakeApi(async () => [host('alice', 'online'), host('bob', 'offline'), host('cy', 'draining')])
    const dash = mountDashboard(root, api, { pollMs: 100000 })
    await dash.refresh()
    const cards = root.querySelectorAll('.host-card')
    expect(cards).toHaveLength(3)
    expect(root.querySelector('.status-online')).not.toBeNull()
    expect(root.querySelector('.status-draining')).not.toBeNull()
    dash.dispose()
  })

  it('revoked host is non-interactive: no open action, shows blocked state (INV12 UI)', async () => {
    const api = fakeApi(async () => [host('gone', 'revoked')])
    const dash = mountDashboard(root, api, { pollMs: 100000 })
    await dash.refresh()
    const card = root.querySelector('[data-status="revoked"]') as HTMLElement
    expect(card.querySelector('.host-open')).toBeNull()
    expect(card.querySelector('.host-blocked')?.textContent).toBe('revoked')
    dash.dispose()
  })

  it('open action routes with the host_id (no foreign-host input path exists — INV1/INV3)', async () => {
    const onOpen = vi.fn()
    const api = fakeApi(async () => [host('alice', 'online')])
    const dash = mountDashboard(root, api, { pollMs: 100000, onOpen })
    await dash.refresh()
    ;(root.querySelector('.host-open') as HTMLButtonElement).click()
    expect(onOpen).toHaveBeenCalledWith('id-alice')
    dash.dispose()
  })

  it('a listHosts rejection shows an error banner and keeps polling', async () => {
    const api = fakeApi(async () => {
      throw new ApiError('http', 'boom', 500)
    })
    const dash = mountDashboard(root, api, { pollMs: 100000 })
    await dash.refresh()
    expect(root.querySelector('.dashboard-error')?.textContent).toContain('Could not load hosts')
    dash.dispose()
  })

  it('never issues a request carrying an account_id (INV3) — listHosts is argument-free', async () => {
    const listHosts = vi.fn(async () => [] as HostRecord[])
    const dash = mountDashboard(root, fakeApi(listHosts), { pollMs: 100000 })
    await dash.refresh()
    expect(listHosts).toHaveBeenCalledWith() // no args ⇒ no client-supplied tenant identity
    dash.dispose()
  })
})
