/**
 * PartyShareMapper Unit Tests
 */

import { PartyShareMapper, PartySharePersistence } from '../../../src/infrastructure/persistence/mappers/party-share.mapper';
import { PartyShare } from '../../../src/domain/entities/party-share.entity';
import {
  ShareId,
  PartyId,
  SessionId,
  Threshold,
  ShareData,
  PublicKey,
} from '../../../src/domain/value-objects';
import { PartyShareType, PartyShareStatus } from '../../../src/domain/enums';

describe('PartyShareMapper', () => {
  let mapper: PartyShareMapper;

  beforeEach(() => {
    mapper = new PartyShareMapper();
  });

  const createTestShareData = () => ShareData.create(
    Buffer.from('encrypted-share-data-here-for-testing'),
    Buffer.from('123456789012'), // 12 bytes IV
    Buffer.from('1234567890123456'), // 16 bytes authTag
  );

  const createTestPublicKey = () => PublicKey.fromHex(
    '03' + '0'.repeat(64), // Compressed public key format (33 bytes)
  );

  const createDomainEntity = (): PartyShare => {
    return PartyShare.create({
      partyId: PartyId.create('user123-server'),
      sessionId: SessionId.generate(),
      shareType: PartyShareType.WALLET,
      shareData: createTestShareData(),
      publicKey: createTestPublicKey(),
      threshold: Threshold.create(3, 2),
    });
  };

  const createPersistenceRecord = (): PartySharePersistence => ({
    id: 'share_1699887766123_abc123xyz',
    partyId: 'user123-server',
    sessionId: '550e8400-e29b-41d4-a716-446655440000',
    shareType: 'wallet',
    shareData: JSON.stringify({
      data: Buffer.from('encrypted-share-data-here-for-testing').toString('base64'),
      iv: Buffer.from('123456789012').toString('base64'),
      authTag: Buffer.from('1234567890123456').toString('base64'),
    }),
    publicKey: '03' + '0'.repeat(64),
    thresholdN: 3,
    thresholdT: 2,
    status: 'active',
    createdAt: new Date('2024-01-01T00:00:00Z'),
    updatedAt: new Date('2024-01-01T00:00:00Z'),
    lastUsedAt: null,
  });

  describe('toPersistence', () => {
    it('should map domain entity to persistence model', () => {
      const entity = createDomainEntity();
      const result = mapper.toPersistence(entity);

      expect(result).toBeDefined();
      expect(result.id).toBe(entity.id.value);
      expect(result.partyId).toBe(entity.partyId.value);
      expect(result.sessionId).toBe(entity.sessionId.value);
      expect(result.shareType).toBe(entity.shareType);
      expect(result.thresholdT).toBe(entity.threshold.t);
      expect(result.thresholdN).toBe(entity.threshold.n);
      expect(result.status).toBe(entity.status);
    });

    it('should convert public key to hex', () => {
      const entity = createDomainEntity();
      const result = mapper.toPersistence(entity);

      expect(result.publicKey).toBeDefined();
      expect(typeof result.publicKey).toBe('string');
      expect(result.publicKey).toMatch(/^[0-9a-f]+$/i);
    });

    it('should serialize share data as JSON string', () => {
      const entity = createDomainEntity();
      const result = mapper.toPersistence(entity);

      expect(typeof result.shareData).toBe('string');
      const parsed = JSON.parse(result.shareData);
      expect(parsed).toHaveProperty('data');
      expect(parsed).toHaveProperty('iv');
      expect(parsed).toHaveProperty('authTag');
    });

    it('should handle revoked status', () => {
      const entity = createDomainEntity();
      entity.revoke('Test revocation reason');
      const result = mapper.toPersistence(entity);

      expect(result.status).toBe(PartyShareStatus.REVOKED);
    });
  });

  describe('toDomain', () => {
    it('should map persistence model to domain entity', () => {
      const record = createPersistenceRecord();
      const result = mapper.toDomain(record);

      expect(result).toBeInstanceOf(PartyShare);
      expect(result.id.value).toBe(record.id);
      expect(result.partyId.value).toBe(record.partyId);
      expect(result.sessionId.value).toBe(record.sessionId);
      expect(result.threshold.t).toBe(record.thresholdT);
      expect(result.threshold.n).toBe(record.thresholdN);
    });

    it('should reconstruct value objects', () => {
      const record = createPersistenceRecord();
      const result = mapper.toDomain(record);

      expect(result.id).toBeDefined();
      expect(result.partyId).toBeDefined();
      expect(result.threshold).toBeDefined();
      expect(result.publicKey).toBeDefined();
      expect(result.shareData).toBeDefined();
    });

    it('should preserve timestamps', () => {
      const record = createPersistenceRecord();
      const result = mapper.toDomain(record);

      expect(result.createdAt).toEqual(record.createdAt);
      expect(result.updatedAt).toEqual(record.updatedAt);
    });

    it('should handle null lastUsedAt', () => {
      const record = createPersistenceRecord();
      record.lastUsedAt = null;

      const result = mapper.toDomain(record);

      expect(result.lastUsedAt).toBeUndefined();
    });
  });

  describe('bidirectional mapping', () => {
    it('should preserve data through round-trip conversion', () => {
      const original = createDomainEntity();
      const persistence = mapper.toPersistence(original);
      const restored = mapper.toDomain(persistence);

      expect(restored.id.value).toBe(original.id.value);
      expect(restored.partyId.value).toBe(original.partyId.value);
      expect(restored.sessionId.value).toBe(original.sessionId.value);
      expect(restored.threshold.t).toBe(original.threshold.t);
      expect(restored.threshold.n).toBe(original.threshold.n);
      expect(restored.publicKey.toHex()).toBe(original.publicKey.toHex());
    });
  });

  describe('toDomainList', () => {
    it('should map array of persistence records to domain entities', () => {
      const records = [
        createPersistenceRecord(),
        {
          ...createPersistenceRecord(),
          id: 'share_1699887766124_def456uvw',
          sessionId: '660e8400-e29b-41d4-a716-446655440001',
        },
      ];

      const results = mapper.toDomainList(records);

      expect(results).toHaveLength(2);
      results.forEach((result) => {
        expect(result).toBeInstanceOf(PartyShare);
      });
    });

    it('should return empty array for empty input', () => {
      const results = mapper.toDomainList([]);
      expect(results).toHaveLength(0);
    });
  });
});
