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

const fakeHost = {
  readFile: vi.fn(),
  writeFile: vi.fn().mockResolvedValue(undefined),
  writeFileCreateOnly: vi.fn(),
  deleteFile: vi.fn(),
  listDir: vi.fn(),
  lastCommit: vi.fn(),
  putBlob: vi.fn(),
  getBlob: vi.fn(),
  deleteBlob: vi.fn(),
};

vi.mock('../session', () => ({
  setCurrent: vi.fn(),
  getCurrent: vi.fn(() => ({ value: 1 })),
  clearCurrent: vi.fn(),
  requireCurrent: vi.fn(),
}));

vi.mock('../vault', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../vault')>();
  return {
    ...actual,
    encryptAndWriteItem: vi.fn().mockResolvedValue(undefined),
    encryptAndWriteManifest: vi.fn().mockResolvedValue(undefined),
  };
});

import { handle, type PopupState } from '../router/popup-only';
import * as vault from '../vault';
import type { Manifest, Item } 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() {
  let counter = 0;
  return {
    parse_lastpass_csv_json: vi.fn().mockReturnValue(JSON.stringify({
      items: [
        sampleItem('GitHub'),
        sampleItem('Gmail'),
      ],
      warnings: [{ row: 3, title: 'No Pass', message: 'missing `password` — skipped' }],
    })),
    new_item_id: vi.fn(() => `newid${++counter}`.padEnd(16, '0')),
  };
}

function sampleItem(title: string): Item {
  return {
    id: 'placeholder-id00',
    title,
    type: 'login',
    tags: [],
    favorite: false,
    created: 1000,
    modified: 1000,
    core: { type: 'login', username: 'u', password: 'p' },
    sections: [],
    attachments: [],
    field_history: {},
  } as unknown as Item;
}

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

  it('returns items + warnings from the WASM bridge', async () => {
    const state: PopupState = {
      manifest: EMPTY_MANIFEST,
      gitHost: fakeHost as never,
      wasm: fakeWasm(),
    };
    const result = await handle(
      { type: 'parse_lastpass_csv', bytes: new ArrayBuffer(8) },
      state,
      FAKE_SENDER,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      const data = result.data as { items: Item[]; warnings: unknown[] };
      expect(data.items).toHaveLength(2);
      expect(data.warnings).toHaveLength(1);
    }
  });

  it('surfaces WASM errors as ok:false', async () => {
    const state: PopupState = {
      manifest: EMPTY_MANIFEST,
      gitHost: fakeHost as never,
      wasm: {
        parse_lastpass_csv_json: vi.fn(() => { throw new Error('bad header'); }),
      },
    };
    const result = await handle(
      { type: 'parse_lastpass_csv', bytes: new ArrayBuffer(0) },
      state,
      FAKE_SENDER,
    );
    expect(result).toEqual({ ok: false, error: 'bad header' });
  });
});

describe('import_lastpass_commit handler', () => {
  beforeEach(() => {
    (vault.encryptAndWriteItem as ReturnType<typeof vi.fn>).mockClear();
    (vault.encryptAndWriteManifest as ReturnType<typeof vi.fn>).mockClear();
    (globalThis as { chrome?: unknown }).chrome = {
      storage: { local: { get: vi.fn().mockResolvedValue({}), set: vi.fn() } },
    };
  });

  it('encrypts + writes each item, manifest last', async () => {
    const state: PopupState = {
      manifest: { ...EMPTY_MANIFEST, items: {} },
      gitHost: fakeHost as never,
      wasm: fakeWasm(),
    };

    const result = await handle(
      {
        type: 'import_lastpass_commit',
        items: [sampleItem('A'), sampleItem('B'), sampleItem('C')],
      },
      state,
      FAKE_SENDER,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      const data = result.data as { summary: { itemCount: number } };
      expect(data.summary.itemCount).toBe(3);
    }
    expect(vault.encryptAndWriteItem).toHaveBeenCalledTimes(3);
    expect(vault.encryptAndWriteManifest).toHaveBeenCalledTimes(1);

    // Manifest must have been re-keyed with the WASM-minted IDs (newid1, newid2, newid3).
    expect(Object.keys(state.manifest!.items)).toHaveLength(3);
    for (const key of Object.keys(state.manifest!.items)) {
      expect(key).toMatch(/^newid\d/);
    }
  });

  it('rejects when vault is locked', async () => {
    const state: PopupState = { manifest: null, gitHost: null, wasm: fakeWasm() };
    const result = await handle(
      { type: 'import_lastpass_commit', items: [sampleItem('X')] },
      state,
      FAKE_SENDER,
    );
    expect(result).toEqual({ ok: false, error: 'vault_locked' });
  });

  it('rejects an empty items list', async () => {
    const state: PopupState = {
      manifest: EMPTY_MANIFEST,
      gitHost: fakeHost as never,
      wasm: fakeWasm(),
    };
    const result = await handle(
      { type: 'import_lastpass_commit', items: [] },
      state,
      FAKE_SENDER,
    );
    expect(result).toEqual({ ok: false, error: 'no items to import' });
  });
});
