/**
 * PartyShare Entity Unit Tests
 */

import { PartyShare } from '../../../src/domain/entities/party-share.entity';
import {
  ShareId,
  PartyId,
  SessionId,
  ShareData,
  PublicKey,
  Threshold,
} from '../../../src/domain/value-objects';
import { PartyShareType, PartyShareStatus } from '../../../src/domain/enums';
import { ShareCreatedEvent, ShareRotatedEvent, ShareRevokedEvent } from '../../../src/domain/events';

describe('PartyShare Entity', () => {
  // Test fixtures
  const createTestShareData = () => ShareData.create(
    Buffer.from('encrypted-share-data-here'),
    Buffer.from('123456789012'), // 12 bytes IV
    Buffer.from('1234567890123456'), // 16 bytes authTag
  );

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

  const createTestThreshold = () => Threshold.create(3, 2);

  const createTestPartyShare = () => PartyShare.create({
    partyId: PartyId.create('user123-server'),
    sessionId: SessionId.create('550e8400-e29b-41d4-a716-446655440000'),
    shareType: PartyShareType.WALLET,
    shareData: createTestShareData(),
    publicKey: createTestPublicKey(),
    threshold: createTestThreshold(),
  });

  describe('create', () => {
    it('should create a valid party share', () => {
      const share = createTestPartyShare();

      expect(share.id).toBeDefined();
      expect(share.id.value).toMatch(/^share_\d+_[a-z0-9]+$/);
      expect(share.partyId.value).toBe('user123-server');
      expect(share.sessionId.value).toBe('550e8400-e29b-41d4-a716-446655440000');
      expect(share.shareType).toBe(PartyShareType.WALLET);
      expect(share.status).toBe(PartyShareStatus.ACTIVE);
      expect(share.threshold.toString()).toBe('2-of-3');
    });

    it('should emit ShareCreatedEvent', () => {
      const share = createTestPartyShare();
      const events = share.domainEvents;

      expect(events).toHaveLength(1);
      expect(events[0]).toBeInstanceOf(ShareCreatedEvent);

      const event = events[0] as ShareCreatedEvent;
      expect(event.shareId).toBe(share.id.value);
      expect(event.partyId).toBe('user123-server');
      expect(event.shareType).toBe(PartyShareType.WALLET);
    });
  });

  describe('reconstruct', () => {
    it('should reconstruct share without emitting events', () => {
      const share = PartyShare.reconstruct({
        id: ShareId.create('share_1699887766123_abc123xyz'),
        partyId: PartyId.create('user123-server'),
        sessionId: SessionId.create('550e8400-e29b-41d4-a716-446655440000'),
        shareType: PartyShareType.WALLET,
        shareData: createTestShareData(),
        publicKey: createTestPublicKey(),
        threshold: createTestThreshold(),
        status: PartyShareStatus.ACTIVE,
        createdAt: new Date('2024-01-01'),
        updatedAt: new Date('2024-01-01'),
      });

      expect(share.id.value).toBe('share_1699887766123_abc123xyz');
      expect(share.domainEvents).toHaveLength(0);
    });
  });

  describe('markAsUsed', () => {
    it('should update lastUsedAt timestamp', () => {
      const share = createTestPartyShare();
      share.clearDomainEvents();

      expect(share.lastUsedAt).toBeUndefined();

      share.markAsUsed();

      expect(share.lastUsedAt).toBeDefined();
      expect(share.lastUsedAt).toBeInstanceOf(Date);
    });

    it('should throw error when share is not active', () => {
      const share = createTestPartyShare();
      share.clearDomainEvents();
      share.revoke('test reason');

      expect(() => share.markAsUsed()).toThrow();
    });
  });

  describe('rotate', () => {
    it('should create new share with rotated status for old share', () => {
      const share = createTestPartyShare();
      share.clearDomainEvents();

      const newShareData = ShareData.create(
        Buffer.from('new-encrypted-share-data'),
        Buffer.from('123456789012'),
        Buffer.from('1234567890123456'),
      );
      const newSessionId = SessionId.create('660e8400-e29b-41d4-a716-446655440001');

      const newShare = share.rotate(newShareData, newSessionId);

      // Old share should be marked as rotated
      expect(share.status).toBe(PartyShareStatus.ROTATED);

      // New share should be active
      expect(newShare.status).toBe(PartyShareStatus.ACTIVE);
      expect(newShare.id.value).not.toBe(share.id.value);
      expect(newShare.publicKey.equals(share.publicKey)).toBe(true);

      // Event should be emitted on new share
      const events = newShare.domainEvents;
      expect(events.some(e => e instanceof ShareRotatedEvent)).toBe(true);
    });

    it('should throw error when share is not active', () => {
      const share = createTestPartyShare();
      share.revoke('test reason');

      const newShareData = createTestShareData();
      const newSessionId = SessionId.create('660e8400-e29b-41d4-a716-446655440001');

      expect(() => share.rotate(newShareData, newSessionId)).toThrow();
    });
  });

  describe('revoke', () => {
    it('should mark share as revoked', () => {
      const share = createTestPartyShare();
      share.clearDomainEvents();

      share.revoke('Security concern');

      expect(share.status).toBe(PartyShareStatus.REVOKED);

      const events = share.domainEvents;
      expect(events).toHaveLength(1);
      expect(events[0]).toBeInstanceOf(ShareRevokedEvent);
    });

    it('should throw error when share is already revoked', () => {
      const share = createTestPartyShare();
      share.revoke('First revocation');

      expect(() => share.revoke('Second revocation')).toThrow('Share is already revoked');
    });
  });

  describe('validateThreshold', () => {
    it('should validate threshold correctly', () => {
      const share = createTestPartyShare();

      // 2-of-3 threshold
      expect(share.validateThreshold(2)).toBe(true);
      expect(share.validateThreshold(3)).toBe(true);
      expect(share.validateThreshold(1)).toBe(false); // Less than t
      expect(share.validateThreshold(4)).toBe(false); // More than n
    });
  });

  describe('isActive', () => {
    it('should return true for active share', () => {
      const share = createTestPartyShare();
      expect(share.isActive()).toBe(true);
    });

    it('should return false for revoked share', () => {
      const share = createTestPartyShare();
      share.revoke('test');
      expect(share.isActive()).toBe(false);
    });
  });

  describe('belongsToParty', () => {
    it('should return true for matching party', () => {
      const share = createTestPartyShare();
      expect(share.belongsToParty(PartyId.create('user123-server'))).toBe(true);
    });

    it('should return false for non-matching party', () => {
      const share = createTestPartyShare();
      expect(share.belongsToParty(PartyId.create('other-server'))).toBe(false);
    });
  });

  describe('clearDomainEvents', () => {
    it('should clear all domain events', () => {
      const share = createTestPartyShare();
      expect(share.domainEvents.length).toBeGreaterThan(0);

      share.clearDomainEvents();
      expect(share.domainEvents).toHaveLength(0);
    });
  });
});
