import '@testing-library/jest-dom/vitest';

import React from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';

let contactsState: Record<string, any> = {};

const mocks = vi.hoisted(() => ({
  buildMailboxClaims: vi.fn(async () => []),
  countDmMailboxes: vi.fn(async () => ({ ok: true, count: 0 })),
  ensureRegisteredDmKey: vi.fn(async () => ({ dhPubKey: 'local-dh', dhAlgo: 'X25519' })),
  fetchDmPublicKey: vi.fn(async () => ({ agent_id: '!sb_peer', dh_pub_key: 'peer-dh', dh_algo: 'X25519' })),
  pollDmMailboxes: vi.fn(async () => ({ ok: true, messages: [] })),
  sendDmMessage: vi.fn(async () => ({ ok: true, transport: 'relay' })),
  sendOffLedgerConsentMessage: vi.fn(async () => ({ ok: true, transport: 'relay' })),
  sharedMailboxToken: vi.fn(async () => 'shared-token'),
  buildContactAcceptMessage: vi.fn(() => 'accept'),
  buildContactDenyMessage: vi.fn(() => 'deny'),
  buildContactOfferMessage: vi.fn(() => 'offer'),
  generateSharedAlias: vi.fn(() => 'alias-123'),
  mergeAliasHistory: vi.fn((history?: string[]) => history || []),
  parseAliasRotateMessage: vi.fn(() => null),
  parseDmConsentMessage: vi.fn(() => null),
  preferredDmPeerId: vi.fn((peerId: string) => peerId),
  allDmPeerIds: vi.fn(() => []),
  purgeBrowserDmState: vi.fn(async () => {}),
  ratchetDecryptDM: vi.fn(async () => {
    throw new Error('no_ratchet_state');
  }),
  ratchetEncryptDM: vi.fn(async () => 'ratchet-ciphertext'),
  addContact: vi.fn(),
  blockContact: vi.fn(),
  decryptDM: vi.fn(async () => 'plaintext'),
  decryptSenderSealPayloadLocally: vi.fn(async () => ''),
  deriveSharedKey: vi.fn(async () => ({})),
  encryptDM: vi.fn(async () => 'ciphertext'),
  getContacts: vi.fn(() => contactsState),
  getDHAlgo: vi.fn(() => 'X25519'),
  getNodeIdentity: vi.fn(() => ({
    nodeId: '!sb_local',
    publicKey: 'local-pub',
    privateKey: 'local-priv',
  })),
  hasSovereignty: vi.fn(() => true),
  hydrateWormholeContacts: vi.fn(async () => contactsState),
  purgeBrowserContactGraph: vi.fn(),
  purgeBrowserSigningMaterial: vi.fn(),
  removeContact: vi.fn(),
  unblockContact: vi.fn(),
  unwrapSenderSealPayload: vi.fn(() => ({ version: 'v2', ephemeralPub: '' })),
  updateContact: vi.fn(),
  verifyNodeIdBindingFromPublicKey: vi.fn(async () => true),
  verifyRawSignature: vi.fn(async () => true),
  getSenderRecoveryState: vi.fn(() => 'verified'),
  recoverSenderSealWithFallback: vi.fn(async () => null),
  requiresSenderRecovery: vi.fn(() => false),
  shouldKeepUnresolvedRequestVisible: vi.fn(() => false),
  shouldPromoteRecoveredSenderForBootstrap: vi.fn(() => false),
  shouldPromoteRecoveredSenderForKnownContact: vi.fn(() => false),
  bootstrapDecryptAccessRequest: vi.fn(async () => 'offer'),
  bootstrapEncryptAccessRequest: vi.fn(async () => 'x3dh1:bootstrap'),
  canUseWormholeBootstrap: vi.fn(async () => false),
  bootstrapWormholeIdentity: vi.fn(async () => ({
    node_id: '!sb_local',
    public_key: 'local-pub',
    public_key_algo: 'Ed25519',
    sequence: 1,
    protocol_version: 'infonet/2',
  })),
  exportWormholeDmInvite: vi.fn(async () => ({
    ok: true,
    invite: {
      event_type: 'dm_invite',
      payload: {
        prekey_lookup_handle: 'handle-123',
        expires_at: 2_000_000_000,
      },
    },
    peer_id: '!sb_local',
    trust_fingerprint: 'trustfp123456',
    prekey_publish_pending: false,
  })),
  fetchWormholeStatus: vi.fn(async () => ({ ready: true, transport_tier: 'private_strong' })),
  fetchWormholeIdentity: vi.fn(async () => ({ node_id: '!sb_local', public_key: 'local-pub' })),
  listWormholeDmInviteHandles: vi.fn(async () => ({ ok: true, addresses: [] })),
  prepareWormholeInteractiveLane: vi.fn(async () => ({
    ready: true,
    settingsEnabled: true,
    transportTier: 'private_transitional',
    identity: { node_id: '!sb_local', public_key: 'local-pub' },
  })),
  importWormholeDmInvite: vi.fn(async () => ({
    ok: true,
    peer_id: '!sb_imported',
    trust_fingerprint: 'invitefp',
    trust_level: 'invite_pinned',
  })),
  renameWormholeDmInviteHandle: vi.fn(async () => ({ ok: true })),
  revokeWormholeDmInviteHandle: vi.fn(async () => ({ ok: true, revoked: true })),
  isWormholeReady: vi.fn(async () => true),
  isWormholeSecureRequired: vi.fn(async () => false),
  issueWormholePairwiseAlias: vi.fn(async () => ({ ok: true, shared_alias: 'alias-123' })),
  openWormholeSenderSeal: vi.fn(async () => ({ sender_id: '!sb_peer', seal_verified: true })),
  writeClipboard: vi.fn(async () => undefined),
}));

vi.mock('@/lib/api', () => ({
  API_BASE: 'http://localhost:8000',
}));

vi.mock('@/mesh/meshDmClient', () => ({
  buildMailboxClaims: mocks.buildMailboxClaims,
  countDmMailboxes: mocks.countDmMailboxes,
  ensureRegisteredDmKey: mocks.ensureRegisteredDmKey,
  fetchDmPublicKey: mocks.fetchDmPublicKey,
  pollDmMailboxes: mocks.pollDmMailboxes,
  sendDmMessage: mocks.sendDmMessage,
  sendOffLedgerConsentMessage: mocks.sendOffLedgerConsentMessage,
  sharedMailboxToken: mocks.sharedMailboxToken,
}));

vi.mock('@/mesh/meshDmConsent', () => ({
  allDmPeerIds: mocks.allDmPeerIds,
  buildContactAcceptMessage: mocks.buildContactAcceptMessage,
  buildContactDenyMessage: mocks.buildContactDenyMessage,
  buildContactOfferMessage: mocks.buildContactOfferMessage,
  generateSharedAlias: mocks.generateSharedAlias,
  mergeAliasHistory: mocks.mergeAliasHistory,
  parseAliasRotateMessage: mocks.parseAliasRotateMessage,
  parseDmConsentMessage: mocks.parseDmConsentMessage,
  preferredDmPeerId: mocks.preferredDmPeerId,
}));

vi.mock('@/mesh/meshDmWorkerClient', () => ({
  purgeBrowserDmState: mocks.purgeBrowserDmState,
  ratchetDecryptDM: mocks.ratchetDecryptDM,
  ratchetEncryptDM: mocks.ratchetEncryptDM,
}));

vi.mock('@/mesh/meshIdentity', () => ({
  addContact: mocks.addContact,
  blockContact: mocks.blockContact,
  decryptDM: mocks.decryptDM,
  decryptSenderSealPayloadLocally: mocks.decryptSenderSealPayloadLocally,
  deriveSharedKey: mocks.deriveSharedKey,
  encryptDM: mocks.encryptDM,
  getContacts: mocks.getContacts,
  getDHAlgo: mocks.getDHAlgo,
  getNodeIdentity: mocks.getNodeIdentity,
  hasSovereignty: mocks.hasSovereignty,
  hydrateWormholeContacts: mocks.hydrateWormholeContacts,
  purgeBrowserContactGraph: mocks.purgeBrowserContactGraph,
  purgeBrowserSigningMaterial: mocks.purgeBrowserSigningMaterial,
  removeContact: mocks.removeContact,
  unblockContact: mocks.unblockContact,
  unwrapSenderSealPayload: mocks.unwrapSenderSealPayload,
  updateContact: mocks.updateContact,
  verifyNodeIdBindingFromPublicKey: mocks.verifyNodeIdBindingFromPublicKey,
  verifyRawSignature: mocks.verifyRawSignature,
}));

vi.mock('@/mesh/requestSenderRecovery', () => ({
  getSenderRecoveryState: mocks.getSenderRecoveryState,
  recoverSenderSealWithFallback: mocks.recoverSenderSealWithFallback,
  requiresSenderRecovery: mocks.requiresSenderRecovery,
  shouldKeepUnresolvedRequestVisible: mocks.shouldKeepUnresolvedRequestVisible,
  shouldPromoteRecoveredSenderForBootstrap: mocks.shouldPromoteRecoveredSenderForBootstrap,
  shouldPromoteRecoveredSenderForKnownContact: mocks.shouldPromoteRecoveredSenderForKnownContact,
}));

vi.mock('@/mesh/wormholeDmBootstrapClient', () => ({
  bootstrapDecryptAccessRequest: mocks.bootstrapDecryptAccessRequest,
  bootstrapEncryptAccessRequest: mocks.bootstrapEncryptAccessRequest,
  canUseWormholeBootstrap: mocks.canUseWormholeBootstrap,
}));

vi.mock('@/mesh/wormholeIdentityClient', () => ({
  bootstrapWormholeIdentity: mocks.bootstrapWormholeIdentity,
  fetchWormholeStatus: mocks.fetchWormholeStatus,
  fetchWormholeIdentity: mocks.fetchWormholeIdentity,
  exportWormholeDmInvite: mocks.exportWormholeDmInvite,
  prepareWormholeInteractiveLane: mocks.prepareWormholeInteractiveLane,
  getWormholeDmInviteImportErrorResult: (error: unknown) =>
    error && typeof error === 'object' && 'result' in (error as Record<string, unknown>)
      ? (((error as Record<string, unknown>).result as Record<string, unknown>) || null)
      : null,
  importWormholeDmInvite: mocks.importWormholeDmInvite,
  isWormholeReady: mocks.isWormholeReady,
  isWormholeSecureRequired: mocks.isWormholeSecureRequired,
  listWormholeDmInviteHandles: mocks.listWormholeDmInviteHandles,
  issueWormholePairwiseAlias: mocks.issueWormholePairwiseAlias,
  openWormholeSenderSeal: mocks.openWormholeSenderSeal,
  renameWormholeDmInviteHandle: mocks.renameWormholeDmInviteHandle,
  revokeWormholeDmInviteHandle: mocks.revokeWormholeDmInviteHandle,
}));

import MessagesView from '@/components/InfonetTerminal/MessagesView';

function renderMessagesView(options?: {
  onOpenDeadDrop?: (peerId: string, opts?: { showSas?: boolean }) => void;
}) {
  return render(<MessagesView onBack={() => {}} onOpenDeadDrop={options?.onOpenDeadDrop} />);
}

async function openComposeForRecipient(recipient: string, body: string) {
  fireEvent.click(screen.getByRole('button', { name: 'COMPOSE' }));
  fireEvent.change(screen.getByLabelText(/Recipient agent ID/i), {
    target: { value: recipient },
  });
  fireEvent.change(screen.getByLabelText(/Message/i), {
    target: { value: body },
  });
  await screen.findByLabelText(/Recipient agent ID/i);
}

describe('MessagesView first-contact trust UX', () => {
  beforeEach(() => {
    cleanup();
    localStorage.clear();
    contactsState = {};
    vi.clearAllMocks();
    Object.defineProperty(navigator, 'clipboard', {
      value: { writeText: mocks.writeClipboard },
      configurable: true,
    });

    mocks.getContacts.mockImplementation(() => contactsState);
    mocks.hydrateWormholeContacts.mockImplementation(async () => contactsState);
    mocks.fetchWormholeStatus.mockResolvedValue({ ready: true, transport_tier: 'private_strong' });
    mocks.bootstrapWormholeIdentity.mockResolvedValue({
      node_id: '!sb_local',
      public_key: 'local-pub',
      public_key_algo: 'Ed25519',
      sequence: 1,
      protocol_version: 'infonet/2',
    });
    mocks.prepareWormholeInteractiveLane.mockResolvedValue({
      ready: true,
      settingsEnabled: true,
      transportTier: 'private_transitional',
      identity: { node_id: '!sb_local', public_key: 'local-pub' },
    });
    mocks.isWormholeSecureRequired.mockResolvedValue(false);
    mocks.getNodeIdentity.mockReturnValue({
      nodeId: '!sb_local',
      publicKey: 'local-pub',
      privateKey: 'local-priv',
    });
    mocks.hasSovereignty.mockReturnValue(true);
    mocks.buildMailboxClaims.mockResolvedValue([]);
    mocks.pollDmMailboxes.mockResolvedValue({ ok: true, messages: [] });
    mocks.countDmMailboxes.mockResolvedValue({ ok: true, count: 0 });
    mocks.ensureRegisteredDmKey.mockResolvedValue({ dhPubKey: 'local-dh', dhAlgo: 'X25519' });
    mocks.fetchDmPublicKey.mockResolvedValue({ agent_id: '!sb_peer', dh_pub_key: 'peer-dh', dh_algo: 'X25519' });
    mocks.sendOffLedgerConsentMessage.mockResolvedValue({ ok: true, transport: 'relay' });
    mocks.canUseWormholeBootstrap.mockResolvedValue(false);
    mocks.exportWormholeDmInvite.mockResolvedValue({
      ok: true,
      invite: {
        event_type: 'dm_invite',
        payload: {
          prekey_lookup_handle: 'handle-123',
          expires_at: 2_000_000_000,
        },
      },
      peer_id: '!sb_local',
      trust_fingerprint: 'trustfp123456',
      prekey_publish_pending: false,
    });
    mocks.listWormholeDmInviteHandles.mockResolvedValue({ ok: true, addresses: [] });
  });

  afterEach(() => {
    cleanup();
  });

  it('blocks unknown first contact until a signed invite is imported', async () => {
    renderMessagesView();
    await openComposeForRecipient('!sb_unknown', 'hello from first contact');

    expect(await screen.findByText('Verified First Contact Required')).toBeInTheDocument();
    expect(
      screen.getByText(/Secure request bootstrap is blocked until you import a signed invite/i),
    ).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Send Secure Mail' })).toBeDisabled();
  });

  it('can jump directly from the downgrade warning into invite import flow', async () => {
    renderMessagesView();
    await openComposeForRecipient('!sb_unknown', 'hello from first contact');

    fireEvent.click(screen.getByRole('button', { name: 'Import Signed Invite' }));

    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();
    expect(screen.getByLabelText(/Local Alias/i)).toHaveValue('!sb_unknown');
  });

  it('does not expose a TOFU downgrade button for first contact anymore', async () => {
    renderMessagesView();
    await openComposeForRecipient('!sb_unknown', 'hello from first contact');

    expect(screen.queryByRole('button', { name: /Explicitly Allow TOFU/i })).not.toBeInTheDocument();
    expect(mocks.sendOffLedgerConsentMessage).not.toHaveBeenCalled();
  });

  it('does not require the TOFU override when the contact is invite-pinned already', async () => {
    contactsState = {
      '!sb_invited': {
        alias: 'Pinned Peer',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedTrustFingerprint: 'abcdef123456',
        invitePinnedRootFingerprint: 'rootabcdef123456',
        invitePinnedRootManifestFingerprint: 'manifestabcdef123456',
        invitePinnedRootWitnessPolicyFingerprint: 'policyabcdef123456',
        invitePinnedRootWitnessThreshold: 2,
        invitePinnedRootWitnessCount: 3,
        invitePinnedRootManifestGeneration: 1,
        invitePinnedRootRotationProven: true,
        invitePinnedAt: 123,
        remotePrekeyFingerprint: 'abcdef123456',
        remotePrekeyRootFingerprint: 'rootabcdef123456',
        remotePrekeyRootManifestFingerprint: 'manifestabcdef123456',
        remotePrekeyRootWitnessPolicyFingerprint: 'policyabcdef123456',
        remotePrekeyRootWitnessThreshold: 2,
        remotePrekeyRootWitnessCount: 3,
        remotePrekeyRootManifestGeneration: 1,
        remotePrekeyRootRotationProven: true,
      },
    };

    renderMessagesView();
    await openComposeForRecipient('!sb_invited', 'hello to pinned peer');

    expect(screen.queryByText('Unverified First Contact')).not.toBeInTheDocument();
    expect(screen.queryByText('ROOT LOCAL QUORUM')).not.toBeInTheDocument();
    expect(screen.queryByText(/Local quorum root rootabcd\.\.123456/i)).not.toBeInTheDocument();
    expect(screen.queryByText(/Fingerprint/i)).not.toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Send Secure Mail' })).toBeEnabled();
  });

  it('sends sealed mail without waiting for the private delivery route', async () => {
    contactsState = {
      '!sb_pinned': {
        alias: 'Pinned Peer',
        blocked: false,
        trust_level: 'invite_pinned',
        dhPubKey: 'peer-dh',
        remotePrekeyFingerprint: 'abcdef123456',
      },
    };
    mocks.fetchWormholeStatus.mockResolvedValue({ ready: false, transport_tier: 'public_degraded' });
    mocks.prepareWormholeInteractiveLane.mockImplementation(
      () =>
        new Promise(() => {
          /* background route prep stays pending */
        }),
    );
    mocks.sendDmMessage.mockResolvedValueOnce({
      ok: true,
      queued: true,
      private_transport_pending: true,
    });

    renderMessagesView();
    await openComposeForRecipient('!sb_pinned', 'hello after warmup');

    const sendButton = screen.getByRole('button', { name: 'Send Secure Mail' });
    await waitFor(() => expect(sendButton).toBeEnabled(), { timeout: 5000 });
    fireEvent.click(sendButton);

    await waitFor(() => expect(mocks.prepareWormholeInteractiveLane).toHaveBeenCalled(), { timeout: 5000 });
    await waitFor(() => expect(mocks.sendDmMessage).toHaveBeenCalled(), { timeout: 5000 });
    await screen.findByText(/Mail sealed locally for Pinned Peer/i, {}, { timeout: 5000 });
    expect(screen.queryByText(/still warming up/i)).not.toBeInTheDocument();
  }, 10000);

  it('repairs the local sending key before sending instead of surfacing backend key jargon', async () => {
    contactsState = {
      '!sb_pinned': {
        alias: 'Pinned Peer',
        blocked: false,
        trust_level: 'invite_pinned',
        dhPubKey: 'peer-dh',
        remotePrekeyFingerprint: 'abcdef123456',
      },
    };
    mocks.ensureRegisteredDmKey
      .mockResolvedValueOnce({ ok: true, dhPubKey: '', dhAlgo: 'X25519', detail: 'Missing DH public key' })
      .mockResolvedValueOnce({ ok: true, dhPubKey: 'local-dh-repaired', dhAlgo: 'X25519' });
    mocks.sendDmMessage.mockResolvedValueOnce({ ok: true, transport: 'relay' });

    renderMessagesView();
    await openComposeForRecipient('!sb_pinned', 'hello after repair');

    fireEvent.click(screen.getByRole('button', { name: 'Send Secure Mail' }));

    await waitFor(() => expect(mocks.ensureRegisteredDmKey).toHaveBeenCalledTimes(2));
    await waitFor(() => expect(mocks.sendDmMessage).toHaveBeenCalled());
    expect(await screen.findByText(/Mail delivered to Pinned Peer/i)).toBeInTheDocument();
    expect(screen.queryByText(/Local DM key is unavailable/i)).not.toBeInTheDocument();
    expect(screen.queryByText(/Missing DH public key/i)).not.toBeInTheDocument();
  });

  it('shows saved contacts without witness-policy implementation detail', async () => {
    contactsState = {
      '!sb_policy': {
        alias: 'Policy Peer',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedTrustFingerprint: 'policyfingerprint123456',
        invitePinnedRootFingerprint: 'rootpolicyabcdef123456',
        invitePinnedRootManifestFingerprint: 'manifestpolicyabcdef123456',
        invitePinnedRootWitnessPolicyFingerprint: 'policyabcdef123456',
        invitePinnedRootWitnessThreshold: 2,
        invitePinnedRootWitnessCount: 1,
        invitePinnedRootManifestGeneration: 1,
        invitePinnedRootRotationProven: true,
        invitePinnedAt: 123,
        remotePrekeyFingerprint: 'policyfingerprint123456',
        remotePrekeyRootFingerprint: 'rootpolicyabcdef123456',
        remotePrekeyRootManifestFingerprint: 'manifestpolicyabcdef123456',
        remotePrekeyRootWitnessPolicyFingerprint: 'policyabcdef123456',
        remotePrekeyRootWitnessThreshold: 2,
        remotePrekeyRootWitnessCount: 1,
        remotePrekeyRootManifestGeneration: 1,
        remotePrekeyRootRotationProven: true,
      },
    };

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    expect(await screen.findByText('Saved Contact')).toBeInTheDocument();
    expect(screen.queryByText(/Witness-policy root rootpoli\.\.123456/i)).not.toBeInTheDocument();
    expect(screen.queryByText(/Witnessed root rootpoli\.\.123456/i)).not.toBeInTheDocument();
  });

  it('hydrates Wormhole contacts on first load even when a local browser identity exists', async () => {
    let wormholeIdentityResolved = false;
    contactsState = {
      '!sb_saved': {
        alias: 'Saved Person',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedPrekeyLookupHandle: 'handle-saved',
        invitePinnedTrustFingerprint: 'savedfingerprint123456',
      },
    };
    mocks.isWormholeSecureRequired.mockResolvedValue(true);
    mocks.fetchWormholeIdentity.mockImplementation(async () => {
      wormholeIdentityResolved = true;
      return { node_id: '!sb_local', public_key: 'local-pub' };
    });
    mocks.hydrateWormholeContacts.mockImplementation(async () =>
      wormholeIdentityResolved ? contactsState : {},
    );

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    expect(await screen.findByText('Saved Person')).toBeInTheDocument();
    expect(screen.queryByText(/No approved secure contacts yet/i)).not.toBeInTheDocument();
    expect(mocks.fetchWormholeIdentity).toHaveBeenCalled();
  });

  it('shows an import-invite shortcut for unpinned contacts in the contact list', async () => {
    contactsState = {
      '!sb_unpinned': {
        alias: 'Weak Peer',
        blocked: false,
        dhPubKey: 'peer-dh',
        trust_level: 'unpinned',
      },
    };

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    const importButton = await screen.findByRole('button', { name: 'Import Invite' });
    fireEvent.click(importButton);
    expect(screen.getByLabelText(/Local Alias/i)).toHaveValue('!sb_unpinned');
  });

  it('surfaces pending contact requests in a top-level requests tab with approve and deny actions', async () => {
    localStorage.setItem(
      'sb_infonet_mailbox_v1:!sb_local',
      JSON.stringify({
        version: 1,
        items: [
          {
            id: 'request-1',
            msgId: 'request-1',
            folder: 'inbox',
            kind: 'request',
            direction: 'inbound',
            senderId: '!sb_requester',
            recipientId: '!sb_local',
            subject: 'Contact request from !sb_requester',
            body: '!sb_requester wants to open a secure mailbox.',
            timestamp: 1_778_624_800,
            read: false,
            transport: 'relay',
            deliveryClass: 'request',
            requestStatus: 'pending',
            requestDhPubKey: 'requester-dh',
            requestDhAlgo: 'X25519',
          },
        ],
      }),
    );
    mocks.addContact.mockImplementation((peerId: string, dhPubKey: string, _alias?: string, dhAlgo?: string) => {
      contactsState[peerId] = {
        alias: 'Requester',
        blocked: false,
        dhPubKey,
        dhAlgo,
        trust_level: 'unpinned',
      };
    });

    renderMessagesView();
    fireEvent.click(await screen.findByRole('button', { name: /REQUESTS/i }));

    expect(await screen.findByText('Contact Requests')).toBeInTheDocument();
    expect(await screen.findByText('1 pending')).toBeInTheDocument();
    expect(await screen.findAllByText('!sb_requester')).toHaveLength(2);
    expect(screen.getByRole('button', { name: 'Deny' })).toBeEnabled();

    fireEvent.click(screen.getByRole('button', { name: 'Approve' }));

    await waitFor(() => expect(mocks.addContact).toHaveBeenCalledWith(
      '!sb_requester',
      'peer-dh',
      undefined,
      'X25519',
    ));
    await waitFor(() =>
      expect(mocks.sendOffLedgerConsentMessage).toHaveBeenCalledWith(
        expect.objectContaining({
          recipientId: '!sb_requester',
          recipientDhPub: 'peer-dh',
        }),
      ),
    );
    expect(await screen.findByText(/Contact accepted: Requester\./i)).toBeInTheDocument();
  });

  it('routes continuity reverify from Secure Messages into Dead Drop with SAS visible', async () => {
    contactsState = {
      '!sb_reverify': {
        alias: 'Broken Root Peer',
        blocked: false,
        trust_level: 'continuity_broken',
        remotePrekeyObservedFingerprint: 'observed123456',
        remotePrekeyObservedRootFingerprint: 'rootobserved123456',
        remotePrekeyRootMismatch: true,
      },
    };
    const onOpenDeadDrop = vi.fn();

    renderMessagesView({ onOpenDeadDrop });
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    const reverifyButton = await screen.findByRole('button', { name: 'REVERIFY NOW' });
    fireEvent.click(reverifyButton);

    expect(onOpenDeadDrop).toHaveBeenCalledWith('!sb_reverify', { showSas: true });
  });

  it('still blocks first contact when legacy verified flags and a dh key are seeded on an unpinned contact', async () => {
    contactsState = {
      '!sb_seeded': {
        alias: 'Seeded Peer',
        blocked: false,
        dhPubKey: 'forged-dh',
        verify_inband: true,
        verify_registry: true,
        verified: true,
        trust_level: 'unpinned',
        trustSummary: {
          state: 'unpinned',
          label: 'UNVERIFIED',
          severity: 'warn',
          detail: 'invite required',
          verifiedFirstContact: false,
          recommendedAction: 'import_invite',
          legacyLookup: false,
          inviteAttested: false,
          registryMismatch: false,
          transparencyConflict: false,
        },
      },
    };

    renderMessagesView();
    await openComposeForRecipient('!sb_seeded', 'hello from forged first contact');

    expect(await screen.findByText('Verified First Contact Required')).toBeInTheDocument();
    expect(
      screen.getByText(/Secure request bootstrap is blocked until you import a signed invite/i),
    ).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Send Secure Mail' })).toBeDisabled();
  });

  it('blocks ambient legacy lookup for verified contacts that still lack an invite handle', async () => {
    contactsState = {
      '!sb_legacy': {
        alias: 'Legacy Peer',
        blocked: false,
        trust_level: 'sas_verified',
        remotePrekeyLookupMode: 'legacy_agent_id',
        trustSummary: {
          state: 'sas_verified',
          label: 'SAS VERIFIED',
          severity: 'good',
          detail: 'legacy lookup still active',
          verifiedFirstContact: true,
          recommendedAction: 'import_invite',
          legacyLookup: true,
          inviteAttested: false,
          registryMismatch: false,
          transparencyConflict: false,
        },
      },
    };

    renderMessagesView();
    await openComposeForRecipient('!sb_legacy', 'hello from a legacy lookup contact');

    fireEvent.click(screen.getByRole('button', { name: 'Send Secure Mail' }));

    expect(
      await screen.findByText(
        /This contact needs their full contact address once before messages can be sent/i,
      ),
    ).toBeInTheDocument();
    expect(mocks.fetchDmPublicKey).not.toHaveBeenCalled();
  });

  it('announces attested invite imports as a saved contact', async () => {
    mocks.importWormholeDmInvite.mockResolvedValueOnce({
      ok: true,
      peer_id: '!sb_attested',
      trust_fingerprint: 'invitefp-attested',
      trust_level: 'invite_pinned',
      contact: {},
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(await screen.findByText(/Contact saved: !sb_attested\./i)).toBeInTheDocument();
    expect(await screen.findByText('Saved Contact')).toBeInTheDocument();
    expect(screen.queryByText(/INVITE PINNED for/i)).not.toBeInTheDocument();
  });

  it('automatically creates a share address and keeps copy actions simple', async () => {
    renderMessagesView();

    expect(await screen.findByText(/Contact address ready/i)).toBeInTheDocument();
    expect(await screen.findByText('handle-123')).toBeInTheDocument();
    expect(screen.getByText(/Signed invite ready/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Copy Short Address/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Copy Full Address/i })).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /Copy Short Address/i }));

    await waitFor(() => expect(mocks.writeClipboard).toHaveBeenCalledWith('handle-123'));
    const copied = String(mocks.writeClipboard.mock.calls.at(-1)?.[0] || '');
    expect(copied).toBe('handle-123');
    expect(screen.queryByText(/shadowbroker\.infonet\.dm\.invite/i)).not.toBeInTheDocument();
  });

  it('does not advertise legacy handle-only addresses as copyable contact addresses', async () => {
    localStorage.setItem(
      'sb_infonet_dm_addresses_v1:!sb_local',
      JSON.stringify({
        version: 1,
        addresses: [
          {
            id: 'legacy-address',
            label: 'Legacy handle',
            handle: 'd8ce691f751817e137066f2a1858e21689b0118f8ec485c1',
            peerId: '',
            trustFingerprint: '',
            inviteBlob: '',
            createdAt: 1_700_000_000,
          },
        ],
      }),
    );

    renderMessagesView();

    expect(await screen.findByText(/Contact address ready/i)).toBeInTheDocument();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    expect(await screen.findByText('Legacy handle')).toBeInTheDocument();
    expect(screen.getByText('Address unavailable locally.')).toBeInTheDocument();
    expect(screen.getAllByRole('button', { name: 'Copy Short' }).some((button) => !button.hasAttribute('disabled'))).toBe(true);
    expect(screen.getAllByRole('button', { name: 'Copy Full' }).some((button) => button.hasAttribute('disabled'))).toBe(true);
  });

  it('sends a contact request from a short address instead of requiring JSON', async () => {
    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: 'f0eee9e9ccf849bcb2d86c0d7a1e0669c75be4e05533b0f6c67' },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Send Request' }));

    await waitFor(() => expect(mocks.sendOffLedgerConsentMessage).toHaveBeenCalled());
    expect(await screen.findByText(/Contact request sent to/i)).toBeInTheDocument();
    expect(mocks.fetchDmPublicKey).toHaveBeenCalledWith(
      'http://localhost:8000',
      '',
      'f0eee9e9ccf849bcb2d86c0d7a1e0669c75be4e05533b0f6c67',
    );
    expect(mocks.sendOffLedgerConsentMessage).toHaveBeenCalled();
    expect(screen.queryByText(/Unexpected number in JSON/i)).not.toBeInTheDocument();
    expect(mocks.importWormholeDmInvite).not.toHaveBeenCalled();
  });

  it('hides pasted signed address JSON until advanced details are opened', async () => {
    const signedAddress = JSON.stringify({
      type: 'shadowbroker.infonet.dm.invite',
      version: 1,
      invite: { event_type: 'dm_invite', payload: {} },
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    const addressField = screen.getByPlaceholderText(/Paste a short address/i);
    fireEvent.paste(addressField, {
      clipboardData: {
        getData: () => signedAddress,
      },
    });

    expect(screen.getByDisplayValue(/Copied address received\. Ready to import\./i)).toBeInTheDocument();
    expect(screen.queryByDisplayValue(/shadowbroker\.infonet\.dm\.invite/i)).not.toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: 'Advanced Details' }));

    expect(screen.getByLabelText('Raw copied contact address')).toHaveValue(signedAddress);
  });

  it('imports a copied address without waiting for secure mail warm-up', async () => {
    mocks.fetchWormholeStatus.mockResolvedValue({ ready: false, transport_tier: 'public_degraded' });
    mocks.prepareWormholeInteractiveLane.mockImplementation(
      () =>
        new Promise(() => {
          /* background warm-up stays pending */
        }),
    );
    mocks.importWormholeDmInvite.mockResolvedValueOnce({
      ok: true,
      peer_id: '!sb_now',
      trust_fingerprint: 'invitefp-now',
      trust_level: 'invite_pinned',
      contact: {},
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(await screen.findByText(/Contact saved: !sb_now\./i)).toBeInTheDocument();
    expect(mocks.importWormholeDmInvite).toHaveBeenCalled();
    expect(screen.queryByText(/Secure mail is still warming up/i)).not.toBeInTheDocument();
  });

  it('saves pending-delivery contacts without showing prekey jargon', async () => {
    mocks.importWormholeDmInvite.mockResolvedValueOnce({
      ok: true,
      peer_id: '!sb_pending',
      trust_fingerprint: 'invitefp-pending',
      trust_level: 'invite_pinned',
      pending_prekey: true,
      detail: 'Contact saved.',
      contact: {
        alias: 'Pending Person',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedPrekeyLookupHandle: 'handle-pending',
        invitePinnedTrustFingerprint: 'invitefp-pending',
        dhPubKey: '',
      },
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(await screen.findByText(/Contact saved: Pending Person\./i)).toBeInTheDocument();
    expect(await screen.findByText('Saved Contact')).toBeInTheDocument();
    expect(screen.queryByText(/prekey/i)).not.toBeInTheDocument();
  });

  it('saves mail locally when a saved contact is not reachable yet', async () => {
    contactsState = {
      '!sb_pending': {
        alias: 'Pending Person',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedPrekeyLookupHandle: 'handle-pending',
        invitePinnedTrustFingerprint: 'invitefp-pending',
        dhPubKey: '',
      },
    };
    mocks.fetchDmPublicKey.mockResolvedValueOnce({ agent_id: '!sb_pending', dh_pub_key: '', dh_algo: 'X25519' });

    renderMessagesView();
    await openComposeForRecipient('!sb_pending', 'hello when ready');

    fireEvent.click(screen.getByRole('button', { name: 'Send Secure Mail' }));

    expect(await screen.findByText(/Mail is saved locally and will send automatically when the contact is reachable/i)).toBeInTheDocument();
    expect(mocks.sendOffLedgerConsentMessage).not.toHaveBeenCalled();
    expect(screen.queryByText(/delivery key has not reached/i)).not.toBeInTheDocument();
  });

  it('removes an approved contact immediately from the visible contact list', { timeout: 30_000 }, async () => {
    contactsState = {
      '!sb_remove': {
        alias: 'Remove Me',
        blocked: false,
        trust_level: 'invite_pinned',
        invitePinnedTrustFingerprint: 'removefingerprint123456',
        dhPubKey: 'peer-dh',
      },
    };
    mocks.removeContact.mockImplementation((peerId: string) => {
      delete contactsState[peerId];
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));

    expect(
      await screen.findByText('Remove Me', undefined, { timeout: 5000 }),
    ).toBeInTheDocument();
    fireEvent.click(screen.getByRole('button', { name: 'Remove' }));

    // The Remove handler dispatches several React state updates in one
    // event:
    //   removeContact(peerId)           — external mutation (mock deletes
    //                                     from contactsState)
    //   setContacts(updater)            — React state update
    //   setComposeStatus(`Removed       — toast text, computed via
    //     contact: ${displayNameForPeer   displayNameForPeer(peerId, contacts)
    //     (peerId, contacts)}.`)         which reads the CLOSED-OVER
    //                                     contacts state
    //
    // The flake history (PRs #226, #237, #261, #262, #265, #294, #303,
    // #304, plus the fd7d6fa push) has two distinct causes:
    //
    //   (a) CI runner starvation — two parallel ci.yml invocations
    //       (direct + workflow_call from docker-publish.yml) starving
    //       each other on the same Actions runner. Fixed structurally
    //       in .github/workflows/ci.yml via a concurrency group.
    //
    //   (b) Alias-resolution race — under certain renders, the closed
    //       -over `contacts` in the Remove handler can see the post-
    //       mutation state (contact already gone), and
    //       displayNameForPeer falls through to return the raw peer
    //       id ("!sb_remove") rather than the alias ("Remove Me").
    //       The toast then renders as "Removed contact: !sb_remove."
    //       which the precise `/Removed contact: Remove Me\./i` regex
    //       missed. We loosen the assertion to match either rendering
    //       — the behavioural guarantee under test is "the removal
    //       toast appears", not "the alias was resolved correctly
    //       at toast-render time". That second property is an
    //       implementation detail the component can reorder freely.
    //
    // The pair of assertions below still proves the real contract:
    // 1. A toast that announces a removal renders.
    // 2. The contact's alias is no longer visible in the contact list.
    //
    // The failure mode this no longer masks is "no toast at all", which
    // still fails loudly at the 10s waitFor cap.
    await waitFor(
      () => {
        expect(
          screen.getByText(/Removed contact:/i),
        ).toBeInTheDocument();
      },
      { timeout: 10000, interval: 50 },
    );
    expect(screen.queryByText('Remove Me')).not.toBeInTheDocument();
  });

  it('explains unresolved address delivery without exposing backend jargon', async () => {
    mocks.importWormholeDmInvite.mockRejectedValueOnce(new Error('peer prekey lookup unavailable'));

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(await screen.findByText(/This address is valid, but contact delivery is not ready on this node yet/i)).toBeInTheDocument();
    expect(screen.queryByText('peer prekey lookup unavailable')).not.toBeInTheDocument();
    expect(screen.queryByText(/sender prekey/i)).not.toBeInTheDocument();
  });

  it('announces compat invite imports as a saved contact without backend detail', async () => {
    mocks.importWormholeDmInvite.mockResolvedValueOnce({
      ok: true,
      peer_id: '!sb_compat',
      trust_fingerprint: 'invitefp-compat',
      trust_level: 'tofu_pinned',
      detail: 'legacy invite imported as tofu_pinned; SAS verification required before first contact',
      contact: {},
    });

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(await screen.findByText(/Contact saved: !sb_compat\./i)).toBeInTheDocument();
    expect(screen.queryByText(/TOFU PINNED for/i)).not.toBeInTheDocument();
    expect(
      screen.queryByText(/legacy invite imported as tofu_pinned; SAS verification required before first contact/i),
    ).not.toBeInTheDocument();
  });

  it('surfaces stable root continuity breaks on invite re-import', async () => {
    contactsState = {
      '!sb_attested': {
        alias: 'Pinned Peer',
        blocked: false,
        trust_level: 'continuity_broken',
        invitePinnedTrustFingerprint: 'oldfingerprint123456',
        invitePinnedRootFingerprint: 'rootold123456',
        remotePrekeyFingerprint: 'newfingerprint654321',
        remotePrekeyObservedFingerprint: 'newfingerprint654321',
        remotePrekeyRootFingerprint: 'rootold123456',
        remotePrekeyObservedRootFingerprint: 'rootnew654321',
        remotePrekeyRootMismatch: true,
      },
    };
    const error = Object.assign(
      new Error(
        'signed invite root continuity mismatch; re-verify SAS or replace the signed invite before trusting this root change',
      ),
      {
        result: {
          ok: false,
          peer_id: '!sb_attested',
          trust_level: 'continuity_broken',
          detail:
            'signed invite root continuity mismatch; re-verify SAS or replace the signed invite before trusting this root change',
          contact: {},
        },
      },
    );
    mocks.importWormholeDmInvite.mockRejectedValueOnce(error);

    renderMessagesView();
    fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
    expect(await screen.findByText("Paste Someone's Address")).toBeInTheDocument();

    fireEvent.change(screen.getByPlaceholderText(/Paste a short address/i), {
      target: { value: JSON.stringify({ invite: { event_type: 'dm_invite', payload: {} } }) },
    });
    fireEvent.click(screen.getByRole('button', { name: 'Import Address' }));

    expect(
      await screen.findByText(/CONTINUITY BROKEN for Pinned Peer\. Stable root continuity changed\./i),
    ).toBeInTheDocument();
    expect(
      screen.getByText(/re-verify SAS in Dead Drop or replace the signed invite before trusting this contact again/i),
    ).toBeInTheDocument();
  });

  it('uses non-blocking secure-mail startup language while the DM lane warms', async () => {
    mocks.fetchWormholeStatus.mockResolvedValue({ ready: false, transport_tier: 'public_degraded' });
    mocks.prepareWormholeInteractiveLane.mockImplementation(
      () =>
        new Promise(() => {
          /* keep background warm-up pending for this assertion */
        }),
    );

    renderMessagesView();

    expect(
      await screen.findByText(/Private delivery route is connecting/i),
    ).toBeInTheDocument();
    expect(screen.getByText(/Addresses, contacts, and sealed sends can proceed now/i)).toBeInTheDocument();
    expect(screen.queryByText(/LOCKED/i)).not.toBeInTheDocument();
    expect(screen.queryByText(/enter the Wormhole/i)).not.toBeInTheDocument();
  });
});
