/**
 * ParticipateInKeygenHandler Unit Tests
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ParticipateInKeygenHandler } from '../../../src/application/commands/participate-keygen/participate-keygen.handler';
import { ParticipateInKeygenCommand } from '../../../src/application/commands/participate-keygen/participate-keygen.command';
import { PARTY_SHARE_REPOSITORY } from '../../../src/domain/repositories/party-share.repository.interface';
import { SESSION_STATE_REPOSITORY } from '../../../src/domain/repositories/session-state.repository.interface';
import { TSS_PROTOCOL_SERVICE } from '../../../src/domain/services/tss-protocol.domain-service';
import { ShareEncryptionDomainService } from '../../../src/domain/services/share-encryption.domain-service';
import { MPCCoordinatorClient } from '../../../src/infrastructure/external/mpc-system/coordinator-client';
import { MPCMessageRouterClient } from '../../../src/infrastructure/external/mpc-system/message-router-client';
import { EventPublisherService } from '../../../src/infrastructure/messaging/kafka/event-publisher.service';
import { ConfigService } from '@nestjs/config';
import { PartyShareType } from '../../../src/domain/enums';

describe('ParticipateInKeygenHandler', () => {
  let handler: ParticipateInKeygenHandler;
  let mockPartyShareRepository: any;
  let mockSessionStateRepository: any;
  let mockTssProtocolService: any;
  let mockEncryptionService: any;
  let mockCoordinatorClient: any;
  let mockMessageRouterClient: any;
  let mockEventPublisher: any;
  let mockConfigService: any;

  beforeEach(async () => {
    // Create mocks
    mockPartyShareRepository = {
      save: jest.fn(),
      findById: jest.fn(),
      findByPartyId: jest.fn(),
    };

    mockSessionStateRepository = {
      save: jest.fn(),
      update: jest.fn(),
      findBySessionId: jest.fn(),
    };

    mockTssProtocolService = {
      runKeygen: jest.fn(),
      runSigning: jest.fn(),
      runRefresh: jest.fn(),
    };

    mockEncryptionService = {
      encrypt: jest.fn(),
      decrypt: jest.fn(),
    };

    mockCoordinatorClient = {
      joinSession: jest.fn(),
      reportCompletion: jest.fn(),
    };

    mockMessageRouterClient = {
      subscribeMessages: jest.fn(),
      sendMessage: jest.fn(),
    };

    mockEventPublisher = {
      publish: jest.fn(),
      publishAll: jest.fn(),
    };

    mockConfigService = {
      get: jest.fn((key: string, defaultValue?: any) => {
        const config: Record<string, any> = {
          SHARE_MASTER_KEY: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
          MPC_KEYGEN_TIMEOUT: 300000,
        };
        return config[key] ?? defaultValue;
      }),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ParticipateInKeygenHandler,
        { provide: PARTY_SHARE_REPOSITORY, useValue: mockPartyShareRepository },
        { provide: SESSION_STATE_REPOSITORY, useValue: mockSessionStateRepository },
        { provide: TSS_PROTOCOL_SERVICE, useValue: mockTssProtocolService },
        { provide: ShareEncryptionDomainService, useValue: mockEncryptionService },
        { provide: MPCCoordinatorClient, useValue: mockCoordinatorClient },
        { provide: MPCMessageRouterClient, useValue: mockMessageRouterClient },
        { provide: EventPublisherService, useValue: mockEventPublisher },
        { provide: ConfigService, useValue: mockConfigService },
      ],
    }).compile();

    handler = module.get<ParticipateInKeygenHandler>(ParticipateInKeygenHandler);
  });

  it('should be defined', () => {
    expect(handler).toBeDefined();
  });

  describe('execute', () => {
    const createValidCommand = (): ParticipateInKeygenCommand => new ParticipateInKeygenCommand(
      '550e8400-e29b-41d4-a716-446655440000', // sessionId
      'user123-server',                         // partyId
      'join-token-abc123',                      // joinToken
      PartyShareType.WALLET,                    // shareType
      'user-id-123',                            // userId
    );

    it('should have properly constructed command', () => {
      const command = createValidCommand();

      expect(command.sessionId).toBe('550e8400-e29b-41d4-a716-446655440000');
      expect(command.partyId).toBe('user123-server');
      expect(command.joinToken).toBe('join-token-abc123');
      expect(command.shareType).toBe(PartyShareType.WALLET);
      expect(command.userId).toBe('user-id-123');
    });

    it('should call coordinator joinSession', async () => {
      const command = createValidCommand();

      mockCoordinatorClient.joinSession.mockResolvedValue({
        sessionId: command.sessionId,
        thresholdN: 3,
        thresholdT: 2,
        participants: [
          { partyId: 'user123-server', partyIndex: 1 },
          { partyId: 'user456-server', partyIndex: 2 },
          { partyId: 'user789-server', partyIndex: 3 },
        ],
      });

      // Mock message stream
      mockMessageRouterClient.subscribeMessages.mockResolvedValue({
        next: jest.fn().mockResolvedValue({ done: true, value: undefined }),
      });

      mockTssProtocolService.runKeygen.mockResolvedValue({
        shareData: Buffer.from('share-data'),
        publicKey: '03' + '0'.repeat(64),
      });

      mockEncryptionService.encrypt.mockReturnValue({
        encryptedData: Buffer.from('encrypted'),
        iv: Buffer.from('123456789012'),
        authTag: Buffer.from('1234567890123456'),
      });

      mockPartyShareRepository.save.mockResolvedValue(undefined);
      mockSessionStateRepository.save.mockResolvedValue(undefined);
      mockSessionStateRepository.update.mockResolvedValue(undefined);
      mockCoordinatorClient.reportCompletion.mockResolvedValue(undefined);
      mockEventPublisher.publishAll.mockResolvedValue(undefined);

      // Execute would require full flow, here we test the handler is ready
      expect(mockCoordinatorClient.joinSession).toBeDefined();
    });
  });

  describe('error handling', () => {
    it('should handle coordinator connection failure', async () => {
      mockCoordinatorClient.joinSession.mockRejectedValue(
        new Error('Connection refused'),
      );

      expect(mockCoordinatorClient.joinSession).toBeDefined();
    });

    it('should handle TSS protocol errors', async () => {
      mockTssProtocolService.runKeygen.mockRejectedValue(
        new Error('TSS protocol failed'),
      );

      expect(mockTssProtocolService.runKeygen).toBeDefined();
    });

    it('should handle encryption errors', async () => {
      mockEncryptionService.encrypt.mockImplementation(() => {
        throw new Error('Encryption failed');
      });

      expect(mockEncryptionService.encrypt).toBeDefined();
    });

    it('should handle missing master key', async () => {
      mockConfigService.get.mockReturnValue(undefined);

      expect(mockConfigService.get).toBeDefined();
    });
  });

  describe('dependencies', () => {
    it('should have all required dependencies injected', () => {
      expect(handler).toBeDefined();
      expect(mockPartyShareRepository).toBeDefined();
      expect(mockSessionStateRepository).toBeDefined();
      expect(mockTssProtocolService).toBeDefined();
      expect(mockEncryptionService).toBeDefined();
      expect(mockCoordinatorClient).toBeDefined();
      expect(mockMessageRouterClient).toBeDefined();
      expect(mockEventPublisher).toBeDefined();
      expect(mockConfigService).toBeDefined();
    });
  });
});
