import { beforeEach, describe, expect, it, vi } from 'vitest'
import { readConfig } from '../src/config'
import { mountStagingLogin } from '../src/login-staging'
import type { DpopKey } from '../src/dpop'

const cfg = readConfig({
  protocol: 'https:',
  host: 'alice.term.example.com',
  hostname: 'alice.term.example.com',
})

/** Deterministic DPoP key so the login test never touches WebCrypto. */
const fakeKey = (jkt: string): DpopKey => ({ jkt, proof: async () => 'h.p.s' })
const FAKE_JKT = 'A'.repeat(43)

function mintFetch(body: unknown, ok = true, status = 200): typeof fetch {
  return vi.fn(async () => ({ ok, status, json: async () => body }) as Response) as unknown as typeof fetch
}

describe('mountStagingLogin (B6) — operator password → POST /auth/mint', () => {
  let root: HTMLElement
  beforeEach(() => {
    root = document.createElement('div')
    document.body.append(root)
  })

  it('correct password → "ok" and POSTs {password, jkt, subdomain} (INV3: a NAME, not an accountId)', async () => {
    const fetchImpl = mintFetch({ token: 'CAPTOKEN' })
    const onSuccess = vi.fn()
    const login = mountStagingLogin(root, cfg, {
      fetchImpl,
      onSuccess,
      createKey: async () => fakeKey(FAKE_JKT),
    })

    await expect(login.submit('s3cret')).resolves.toBe('ok')

    const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0]!
    expect(call[0]).toBe('/auth/mint')
    const init = call[1] as RequestInit
    expect(init.method).toBe('POST')
    expect(JSON.parse(init.body as string)).toEqual({
      password: 's3cret',
      jkt: FAKE_JKT,
      subdomain: 'alice',
    })
    // onSuccess receives the token + aud + the SAME dpop key whose jkt was minted.
    const session = onSuccess.mock.calls[0]![0]
    expect(session.token).toBe('CAPTOKEN')
    expect(session.aud).toBe('alice')
    expect(session.dpop.jkt).toBe(FAKE_JKT)
  })

  it('wrong password (401) → "rejected", no onSuccess, error via textContent (no innerHTML)', async () => {
    const onSuccess = vi.fn()
    const login = mountStagingLogin(root, cfg, {
      fetchImpl: mintFetch({}, false, 401),
      onSuccess,
      createKey: async () => fakeKey(FAKE_JKT),
    })
    await expect(login.submit('nope')).resolves.toBe('rejected')
    expect(onSuccess).not.toHaveBeenCalled()
    const err = root.querySelector('.login-error') as HTMLElement
    expect(err.textContent).toBe('Invalid operator password.')
    expect(err.innerHTML).toBe('Invalid operator password.') // textContent-set, no markup
  })

  it('empty input keeps submit disabled and submit("") rejects without a fetch or a key', async () => {
    const fetchImpl = mintFetch({ token: 'X' })
    const createKey = vi.fn(async () => fakeKey(FAKE_JKT))
    const login = mountStagingLogin(root, cfg, { fetchImpl, createKey })
    expect((root.querySelector('button') as HTMLButtonElement).disabled).toBe(true)
    await expect(login.submit('')).resolves.toBe('rejected')
    expect(fetchImpl).not.toHaveBeenCalled()
    expect(createKey).not.toHaveBeenCalled()
  })

  it('network error → "rejected" with a retry message', async () => {
    const fetchImpl = vi.fn(async () => {
      throw new Error('offline')
    }) as unknown as typeof fetch
    const login = mountStagingLogin(root, cfg, { fetchImpl, createKey: async () => fakeKey(FAKE_JKT) })
    await expect(login.submit('s3cret')).resolves.toBe('rejected')
    expect((root.querySelector('.login-error') as HTMLElement).textContent).toBe(
      'Network error — please retry.',
    )
  })

  it('malformed mint response (no token) → "rejected", no onSuccess', async () => {
    const onSuccess = vi.fn()
    const login = mountStagingLogin(root, cfg, {
      fetchImpl: mintFetch({ nope: true }),
      onSuccess,
      createKey: async () => fakeKey(FAKE_JKT),
    })
    await expect(login.submit('s3cret')).resolves.toBe('rejected')
    expect(onSuccess).not.toHaveBeenCalled()
    expect((root.querySelector('.login-error') as HTMLElement).textContent).toBe(
      'Malformed response from the relay.',
    )
  })

  it('crypto failure (key gen throws) → "rejected", no fetch issued', async () => {
    const fetchImpl = mintFetch({ token: 'X' })
    const login = mountStagingLogin(root, cfg, {
      fetchImpl,
      createKey: async () => {
        throw new Error('no webcrypto')
      },
    })
    await expect(login.submit('s3cret')).resolves.toBe('rejected')
    expect(fetchImpl).not.toHaveBeenCalled()
    expect((root.querySelector('.login-error') as HTMLElement).textContent).toBe(
      'Secure crypto is unavailable in this browser.',
    )
  })

  it('does not persist the password to localStorage', async () => {
    const login = mountStagingLogin(root, cfg, {
      fetchImpl: mintFetch({ token: 'X' }),
      createKey: async () => fakeKey(FAKE_JKT),
    })
    await login.submit('s3cret')
    expect(JSON.stringify(localStorage)).not.toContain('s3cret')
  })
})
