/**
 * Shared test helpers: a valid PanelConfig factory, an authed session cookie, an Ed25519 PKCS#8 PEM
 * generator (for the manage-token test), and simple fakes for the CP client + token minter.
 */
import { SESSION_COOKIE_NAME } from '../src/security/cookies.js'
import { createSessionToken } from '../src/security/session.js'
import type { PanelConfig } from '../src/config.js'
import type { CpClient, HostView, IssuedPairing } from '../src/cp-client.js'
import type { ManageTokenMinter } from '../src/manage-token.js'

export const TEST_SESSION_SECRET = 'test-session-secret-0123456789'

export function makeConfig(overrides: Partial<PanelConfig> = {}): PanelConfig {
  return {
    panelPassword: 'hunter2-correct-horse',
    sessionSecret: TEST_SESSION_SECRET,
    cpUrl: 'http://127.0.0.1:8080',
    baseDomain: 'terminal.yaojia.wang',
    operatorAccountId: 'acct-operator-001',
    capabilitySignKeyPath: '/nonexistent/key.pem',
    tunnelZone: 'terminal.yaojia.wang',
    panelBindPort: 8090,
    ...overrides,
  }
}

/** A valid session cookie header value for injection (`cookie` header). */
export function authCookieHeader(secret: string = TEST_SESSION_SECRET, nowMs: number = Date.now()): string {
  return `${SESSION_COOKIE_NAME}=${createSessionToken(secret, nowMs)}`
}

/** A minter that returns a fixed opaque token and records how many times it was called. */
export function fakeMinter(token = 'fake.manage.token'): ManageTokenMinter & { readonly calls: () => number } {
  let n = 0
  return {
    async mint() {
      n += 1
      return token
    },
    calls: () => n,
  }
}

export interface FakeCpCall {
  readonly method: string
  readonly accountIdOrHostId: string
  readonly token: string
}

/** A CP client fake that records calls and returns canned data (or throws an injected error). */
export function fakeCpClient(opts: {
  hosts?: readonly HostView[]
  pairing?: IssuedPairing
  throwErr?: Error
} = {}): CpClient & { readonly calls: readonly FakeCpCall[] } {
  const calls: FakeCpCall[] = []
  const guard = (): void => {
    if (opts.throwErr) throw opts.throwErr
  }
  return {
    calls,
    async listHosts(accountId, token) {
      calls.push({ method: 'listHosts', accountIdOrHostId: accountId, token })
      guard()
      return opts.hosts ?? []
    },
    async createPairingCode(accountId, token) {
      calls.push({ method: 'createPairingCode', accountIdOrHostId: accountId, token })
      guard()
      return opts.pairing ?? { code: 'ABCD-EFGH', expiresAt: '2026-01-01T00:00:00.000Z' }
    },
    async deleteHost(hostId, token) {
      calls.push({ method: 'deleteHost', accountIdOrHostId: hostId, token })
      guard()
    },
  }
}

/** Generate an Ed25519 PKCS#8 PEM (private key) for signing-key tests. */
export async function generatePkcs8Pem(): Promise<{ pem: string; publicRaw: Uint8Array }> {
  const pair = (await globalThis.crypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify'])) as unknown as {
    publicKey: CryptoKey
    privateKey: CryptoKey
  }
  const pkcs8 = new Uint8Array(await globalThis.crypto.subtle.exportKey('pkcs8', pair.privateKey))
  const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey('raw', pair.publicKey))
  const b64 = Buffer.from(pkcs8).toString('base64')
  const lines = b64.match(/.{1,64}/g) ?? [b64]
  const pem = `-----BEGIN PRIVATE KEY-----\n${lines.join('\n')}\n-----END PRIVATE KEY-----\n`
  return { pem, publicRaw: raw }
}
