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

// Mock IndexedDB storage functions before importing store
vi.mock('@/lib/smime/key-storage', () => ({
  saveKeyRecord: vi.fn().mockResolvedValue(undefined),
  listKeyRecords: vi.fn().mockResolvedValue([]),
  deleteKeyRecord: vi.fn().mockResolvedValue(undefined),
  savePublicCert: vi.fn().mockResolvedValue(undefined),
  listPublicCerts: vi.fn().mockResolvedValue([]),
  deletePublicCert: vi.fn().mockResolvedValue(undefined),
}));

vi.mock('@/lib/smime/pkcs12-import', () => ({
  importPkcs12: vi.fn(),
  unlockPrivateKey: vi.fn(),
}));

vi.mock('@/lib/smime/certificate-utils', () => ({
  parseCertificatePemOrDer: vi.fn(),
  extractCertificateInfo: vi.fn(),
}));

import { useSmimeStore } from '@/stores/smime-store';
import { listKeyRecords, listPublicCerts, saveKeyRecord, deleteKeyRecord, deletePublicCert } from '@/lib/smime/key-storage';
import { importPkcs12, unlockPrivateKey } from '@/lib/smime/pkcs12-import';
import type { SmimeKeyRecord, SmimePublicCert } from '@/lib/smime/types';

const mockKeyRecord: SmimeKeyRecord = {
  id: 'key-1',
  email: 'user@example.com',
  certificate: new ArrayBuffer(10),
  certificateChain: [],
  encryptedPrivateKey: new ArrayBuffer(32),
  salt: new ArrayBuffer(16),
  iv: new ArrayBuffer(12),
  kdfIterations: 600000,
  issuer: 'CN=Test CA',
  subject: 'CN=Test User',
  serialNumber: '01',
  notBefore: '2024-01-01T00:00:00Z',
  notAfter: '2030-12-31T23:59:59Z',
  fingerprint: 'aa:bb:cc',
  algorithm: 'RSA-2048',
  capabilities: { canSign: true, canEncrypt: true },
};

beforeEach(() => {
  localStorage.clear();
  sessionStorage.clear();
  // Reset store state
  useSmimeStore.setState({
    keyRecords: [],
    publicCerts: [],
    unlockedKeys: new Map(),
    unlockedDecryptionKeys: new Map(),
    identityKeyBindings: {},
    defaultSignIdentity: {},
    defaultEncrypt: false,
    rememberUnlockedKeys: false,
    autoImportSignerCerts: true,
    accountPreferences: {},
    currentAccountId: null,
    isLoading: false,
    error: null,
  });
  vi.clearAllMocks();
});

describe('smime-store', () => {
  describe('load', () => {
    it('loads key records and public certs from IndexedDB', async () => {
      const records = [mockKeyRecord];
      const certs: SmimePublicCert[] = [];
      vi.mocked(listKeyRecords).mockResolvedValue(records);
      vi.mocked(listPublicCerts).mockResolvedValue(certs);

      await useSmimeStore.getState().load();

      const state = useSmimeStore.getState();
      expect(state.keyRecords).toEqual(records);
      expect(state.publicCerts).toEqual(certs);
      expect(state.isLoading).toBe(false);
    });

    it('re-unlocks remembered keys during load', async () => {
      const records = [mockKeyRecord];
      const mockSigningKey = {} as CryptoKey;
      const mockDecryptionKey = {} as CryptoKey;
      sessionStorage.setItem('smime-unlocked-session', JSON.stringify({ 'key-1': 'passphrase' }));
      useSmimeStore.setState({ rememberUnlockedKeys: true });
      vi.mocked(listKeyRecords).mockResolvedValue(records);
      vi.mocked(listPublicCerts).mockResolvedValue([]);
      vi.mocked(unlockPrivateKey).mockResolvedValue({
        signingKey: mockSigningKey,
        decryptionKey: mockDecryptionKey,
      });

      await useSmimeStore.getState().load();

      expect(unlockPrivateKey).toHaveBeenCalledWith(mockKeyRecord, 'passphrase');
      expect(useSmimeStore.getState().getUnlockedKey('key-1')).toBe(mockSigningKey);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.get('key-1')).toBe(mockDecryptionKey);
    });

    it('removes stale remembered keys when re-unlock fails', async () => {
      const records = [mockKeyRecord];
      sessionStorage.setItem('smime-unlocked-session', JSON.stringify({ 'key-1': 'bad-pass' }));
      useSmimeStore.setState({ rememberUnlockedKeys: true });
      vi.mocked(listKeyRecords).mockResolvedValue(records);
      vi.mocked(listPublicCerts).mockResolvedValue([]);
      vi.mocked(unlockPrivateKey).mockRejectedValue(new Error('Incorrect passphrase'));

      await useSmimeStore.getState().load();

      expect(sessionStorage.getItem('smime-unlocked-session')).toBeNull();
      expect(useSmimeStore.getState().isKeyUnlocked('key-1')).toBe(false);
    });

    it('sets error on failure', async () => {
      vi.mocked(listKeyRecords).mockRejectedValue(new Error('DB failed'));

      await useSmimeStore.getState().load();

      expect(useSmimeStore.getState().error).toBe('DB failed');
      expect(useSmimeStore.getState().isLoading).toBe(false);
    });
  });

  describe('importPKCS12', () => {
    it('imports and adds key record', async () => {
      vi.mocked(importPkcs12).mockResolvedValue({
        keyRecord: mockKeyRecord,
        certInfo: {} as unknown as import('@/lib/smime/types').CertificateInfo,
      });

      const result = await useSmimeStore.getState().importPKCS12(
        new ArrayBuffer(10),
        'p12pass',
        'storagepass',
      );

      expect(result.id).toBe('key-1');
      expect(saveKeyRecord).toHaveBeenCalledWith(mockKeyRecord);
      expect(useSmimeStore.getState().keyRecords).toHaveLength(1);
    });

    it('sets error on import failure', async () => {
      vi.mocked(importPkcs12).mockRejectedValue(new Error('Bad password'));

      await expect(
        useSmimeStore.getState().importPKCS12(new ArrayBuffer(10), 'wrong', 'pass'),
      ).rejects.toThrow('Bad password');

      expect(useSmimeStore.getState().error).toBe('Bad password');
    });
  });

  describe('removeKeyRecord', () => {
    it('removes key record and clears bindings', async () => {
      useSmimeStore.setState({
        keyRecords: [mockKeyRecord],
        identityKeyBindings: { 'identity-1': 'key-1' },
        unlockedKeys: new Map([['key-1', {} as CryptoKey]]),
        unlockedDecryptionKeys: new Map([['key-1', {} as CryptoKey]]),
      });

      await useSmimeStore.getState().removeKeyRecord('key-1');

      expect(deleteKeyRecord).toHaveBeenCalledWith('key-1');
      expect(useSmimeStore.getState().keyRecords).toHaveLength(0);
      expect(useSmimeStore.getState().identityKeyBindings).toEqual({});
      expect(useSmimeStore.getState().unlockedKeys.has('key-1')).toBe(false);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.has('key-1')).toBe(false);
    });
  });

  describe('removePublicCert', () => {
    it('removes public cert', async () => {
      const cert: SmimePublicCert = {
        id: 'cert-1',
        email: 'recipient@example.com',
        certificate: new ArrayBuffer(10),
        issuer: 'CN=CA',
        subject: 'CN=Recipient',
        notBefore: '2024-01-01T00:00:00Z',
        notAfter: '2030-12-31T23:59:59Z',
        fingerprint: 'aa:bb',
        source: 'manual',
      };
      useSmimeStore.setState({ publicCerts: [cert] });

      await useSmimeStore.getState().removePublicCert('cert-1');

      expect(deletePublicCert).toHaveBeenCalledWith('cert-1');
      expect(useSmimeStore.getState().publicCerts).toHaveLength(0);
    });
  });

  describe('unlockKey + lockKey', () => {
    it('unlocks a key', async () => {
      const mockSigningKey = {} as CryptoKey;
      const mockDecryptionKey = {} as CryptoKey;
      vi.mocked(unlockPrivateKey).mockResolvedValue({ signingKey: mockSigningKey, decryptionKey: mockDecryptionKey });
      useSmimeStore.setState({ keyRecords: [mockKeyRecord] });

      await useSmimeStore.getState().unlockKey('key-1', 'passphrase');

      expect(useSmimeStore.getState().isKeyUnlocked('key-1')).toBe(true);
      expect(useSmimeStore.getState().getUnlockedKey('key-1')).toBe(mockSigningKey);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.get('key-1')).toBe(mockDecryptionKey);
    });

    it('stores the passphrase for session rehydration when remember is enabled', async () => {
      const mockSigningKey = {} as CryptoKey;
      vi.mocked(unlockPrivateKey).mockResolvedValue({ signingKey: mockSigningKey });
      useSmimeStore.setState({ keyRecords: [mockKeyRecord], rememberUnlockedKeys: true });

      await useSmimeStore.getState().unlockKey('key-1', 'passphrase');

      expect(sessionStorage.getItem('smime-unlocked-session')).toBe(
        JSON.stringify({ 'key-1': 'passphrase' }),
      );
    });

    it('stores only the signing key when no decryption key is available', async () => {
      const mockSigningKey = {} as CryptoKey;
      vi.mocked(unlockPrivateKey).mockResolvedValue({ signingKey: mockSigningKey });
      useSmimeStore.setState({ keyRecords: [mockKeyRecord] });

      await useSmimeStore.getState().unlockKey('key-1', 'passphrase');

      expect(useSmimeStore.getState().getUnlockedKey('key-1')).toBe(mockSigningKey);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.has('key-1')).toBe(false);
    });

    it('throws for non-existent key record', async () => {
      await expect(
        useSmimeStore.getState().unlockKey('non-existent', 'pass'),
      ).rejects.toThrow('Key record not found');
    });

    it('locks a key', () => {
      sessionStorage.setItem('smime-unlocked-session', JSON.stringify({ 'key-1': 'passphrase' }));
      useSmimeStore.setState({
        unlockedKeys: new Map([['key-1', {} as CryptoKey]]),
        unlockedDecryptionKeys: new Map([['key-1', {} as CryptoKey]]),
      });

      useSmimeStore.getState().lockKey('key-1');

      expect(useSmimeStore.getState().isKeyUnlocked('key-1')).toBe(false);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.has('key-1')).toBe(false);
      expect(sessionStorage.getItem('smime-unlocked-session')).toBeNull();
    });

    it('locks all keys', () => {
      sessionStorage.setItem(
        'smime-unlocked-session',
        JSON.stringify({ 'key-1': 'one', 'key-2': 'two' }),
      );
      useSmimeStore.setState({
        unlockedKeys: new Map([
          ['key-1', {} as CryptoKey],
          ['key-2', {} as CryptoKey],
        ]),
        unlockedDecryptionKeys: new Map([
          ['key-1', {} as CryptoKey],
          ['key-2', {} as CryptoKey],
        ]),
      });

      useSmimeStore.getState().lockAllKeys();

      expect(useSmimeStore.getState().unlockedKeys.size).toBe(0);
      expect(useSmimeStore.getState().unlockedDecryptionKeys.size).toBe(0);
      expect(sessionStorage.getItem('smime-unlocked-session')).toBeNull();
    });
  });

  describe('identity bindings', () => {
    it('binds an identity to a key', () => {
      useSmimeStore.getState().bindIdentityToKey('identity-1', 'key-1');
      expect(useSmimeStore.getState().identityKeyBindings['identity-1']).toBe('key-1');
    });

    it('unbinds an identity', () => {
      useSmimeStore.setState({ identityKeyBindings: { 'identity-1': 'key-1' } });
      useSmimeStore.getState().bindIdentityToKey('identity-1', null);
      expect(useSmimeStore.getState().identityKeyBindings['identity-1']).toBeUndefined();
    });

    it('getKeyRecordForIdentity returns the bound record', () => {
      useSmimeStore.setState({
        keyRecords: [mockKeyRecord],
        identityKeyBindings: { 'identity-1': 'key-1' },
      });

      const record = useSmimeStore.getState().getKeyRecordForIdentity('identity-1');
      expect(record?.id).toBe('key-1');
    });

    it('getKeyRecordForIdentity returns undefined for unbound identity', () => {
      const record = useSmimeStore.getState().getKeyRecordForIdentity('identity-2');
      expect(record).toBeUndefined();
    });
  });

  describe('getPublicCertForEmail', () => {
    it('finds cert by email (case-insensitive)', () => {
      const cert: SmimePublicCert = {
        id: 'c1',
        email: 'bob@example.com',
        certificate: new ArrayBuffer(10),
        issuer: 'CN=CA',
        subject: 'CN=Bob',
        notBefore: '2024-01-01',
        notAfter: '2030-12-31',
        fingerprint: 'ff',
        source: 'manual',
      };
      useSmimeStore.setState({ publicCerts: [cert] });

      expect(useSmimeStore.getState().getPublicCertForEmail('Bob@Example.COM')?.id).toBe('c1');
    });

    it('returns undefined when not found', () => {
      expect(useSmimeStore.getState().getPublicCertForEmail('nobody@test.com')).toBeUndefined();
    });
  });

  describe('getRecipientCerts', () => {
    it('partitions emails into found and missing', () => {
      const cert: SmimePublicCert = {
        id: 'c1',
        email: 'known@example.com',
        certificate: new ArrayBuffer(10),
        issuer: '',
        subject: '',
        notBefore: '',
        notAfter: '',
        fingerprint: '',
        source: 'manual',
      };
      useSmimeStore.setState({ publicCerts: [cert] });

      const { found, missing } = useSmimeStore.getState().getRecipientCerts([
        'known@example.com',
        'unknown@example.com',
      ]);

      expect(found).toHaveLength(1);
      expect(found[0].id).toBe('c1');
      expect(missing).toEqual(['unknown@example.com']);
    });
  });

  describe('preferences', () => {
    it('sets sign default for identity', () => {
      useSmimeStore.getState().setSignDefault('identity-1', true);
      expect(useSmimeStore.getState().defaultSignIdentity['identity-1']).toBe(true);
    });

    it('sets encrypt default', () => {
      useSmimeStore.getState().setEncryptDefault(true);
      expect(useSmimeStore.getState().defaultEncrypt).toBe(true);
    });

    it('sets remember unlocked keys and clears when disabled', () => {
      sessionStorage.setItem('smime-unlocked-session', JSON.stringify({ 'key-1': 'passphrase' }));
      useSmimeStore.setState({
        unlockedKeys: new Map([['key-1', {} as CryptoKey]]),
      });

      useSmimeStore.getState().setRememberUnlockedKeys(false);

      expect(useSmimeStore.getState().rememberUnlockedKeys).toBe(false);
      expect(useSmimeStore.getState().unlockedKeys.size).toBe(0);
      expect(sessionStorage.getItem('smime-unlocked-session')).toBeNull();
    });

    it('sets auto import signer certs', () => {
      useSmimeStore.getState().setAutoImportSignerCerts(true);
      expect(useSmimeStore.getState().autoImportSignerCerts).toBe(true);
    });
  });

  describe('setError', () => {
    it('sets and clears error', () => {
      useSmimeStore.getState().setError('Something went wrong');
      expect(useSmimeStore.getState().error).toBe('Something went wrong');

      useSmimeStore.getState().setError(null);
      expect(useSmimeStore.getState().error).toBeNull();
    });
  });
});
