import { beforeEach, describe, expect, it, vi } from 'vitest';

// Top-level mocks: hoisted before the SUT import.
const fakeNewHost = {
  readFile: vi.fn().mockRejectedValue(new Error('not-found')),
  writeFile: vi.fn().mockResolvedValue(undefined),
  writeFileCreateOnly: vi.fn().mockResolvedValue(undefined),
  deleteFile: vi.fn(),
  listDir: vi.fn().mockResolvedValue([]),
  lastCommit: vi.fn().mockResolvedValue(null),
  putBlob: vi.fn(),
  getBlob: vi.fn(),
  deleteBlob: vi.fn(),
};

vi.mock('../git-host', async () => {
  const actual = await vi.importActual<typeof import('../git-host')>('../git-host');
  return {
    ...actual,
    createGitHost: () => fakeNewHost,
  };
});

vi.mock('../vault', async () => {
  const actual = await vi.importActual<typeof import('../vault')>('../vault');
  return {
    ...actual,
    fetchVaultStateForBackup: vi.fn().mockResolvedValue({
      salt_b64: 'AAAA',
      params_json: '{}',
      devices_json: '[]',
      manifest_enc_b64: 'bWZzdA==',
      settings_enc_b64: 'c3RuZw==',
      items: [{ id: 'aaa1', ciphertext_b64: 'aXRlbS1jdA==' }],
      attachments: [],
    }),
    fetchVaultMeta: vi.fn().mockRejectedValue(new Error('no vault')),
  };
});

import { handle, type PopupState } from '../router/popup-only';
import type { Manifest } from '../../shared/types';

const FAKE_SENDER = {
  url: 'chrome-extension://x/vault.html',
  id: 'x',
  frameId: 0,
} as unknown as chrome.runtime.MessageSender;

const EMPTY_MANIFEST: Manifest = { schema_version: 2, items: {} } as Manifest;

function fakeWasm() {
  return {
    pack_backup_json: vi.fn().mockReturnValue(new Uint8Array([0x52, 0x42, 0x41, 0x4b, 0x01])),
    unpack_backup_json: vi.fn().mockReturnValue(JSON.stringify({
      salt: btoa(String.fromCharCode(...new Uint8Array(32))),
      params_json: '{}',
      devices_json: '[]',
      manifest_enc: btoa('mfst'),
      settings_enc: btoa('stng'),
      items: [{ id: 'aaa1', ciphertext: btoa('item-ct') }],
      attachments: [{ item_id: 'aaa1', attachment_id: 'bbbb', ciphertext: btoa('att-ct') }],
      reference_jpg: null,
    })),
  };
}

describe('export_backup handler', () => {
  beforeEach(() => {
    (globalThis as { chrome?: unknown }).chrome = {
      storage: {
        local: {
          get: vi.fn().mockResolvedValue({}),
          set: vi.fn().mockResolvedValue(undefined),
        },
      },
    };
  });

  it('returns ArrayBuffer of pack output on success', async () => {
    const state: PopupState = {
      manifest: EMPTY_MANIFEST,
      gitHost: fakeNewHost as never,
      wasm: fakeWasm(),
    };

    const result = await handle(
      { type: 'export_backup', passphrase: 'p', includeImage: false },
      state,
      FAKE_SENDER,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      const data = result.data as { bytes: ArrayBuffer };
      expect(data.bytes.byteLength).toBe(5);
    }
  });

  it('rejects when manifest is missing (vault_locked)', async () => {
    const state: PopupState = {
      manifest: null,
      gitHost: fakeNewHost as never,
      wasm: fakeWasm(),
    };
    const result = await handle(
      { type: 'export_backup', passphrase: 'p', includeImage: false },
      state,
      FAKE_SENDER,
    );
    expect(result).toEqual({ ok: false, error: 'vault_locked' });
  });
});

describe('restore_backup handler', () => {
  beforeEach(() => {
    fakeNewHost.writeFileCreateOnly.mockClear();
    (globalThis as { chrome?: unknown }).chrome = {
      storage: {
        local: {
          get: vi.fn().mockResolvedValue({}),
          set: vi.fn().mockResolvedValue(undefined),
        },
      },
    };
  });

  it('writes vault layout via writeFileCreateOnly', async () => {
    const state: PopupState = {
      manifest: null,
      gitHost: null as never,
      wasm: fakeWasm(),
    };
    const result = await handle(
      {
        type: 'restore_backup',
        bytes: new ArrayBuffer(5),
        passphrase: 'p',
        newRemote: { hostType: 'gitea', hostUrl: 'https://x', repoPath: 'a/b', apiToken: 't' },
      },
      state,
      FAKE_SENDER,
    );
    expect(result.ok).toBe(true);
    // 5 baseline files (salt, params, devices, manifest, settings) +
    // 1 item + 1 attachment = 7 writes.
    expect(fakeNewHost.writeFileCreateOnly).toHaveBeenCalledTimes(7);
    // Confirm flat-layout attachment path (not <item_id>/<aid>).
    const attachCall = fakeNewHost.writeFileCreateOnly.mock.calls.find(
      (c: unknown[]) => typeof c[0] === 'string' && (c[0] as string).startsWith('attachments/'),
    );
    expect(attachCall![0]).toBe('attachments/bbbb.bin');
  });
});
