import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { GetBackupShareHandler } from '../../../src/application/queries/get-backup-share/get-backup-share.handler';
import { GetBackupShareQuery } from '../../../src/application/queries/get-backup-share/get-backup-share.query';
import { BACKUP_SHARE_REPOSITORY, BackupShare, BackupShareStatus } from '../../../src/domain';
import { AesEncryptionService } from '../../../src/infrastructure/crypto/aes-encryption.service';
import { AuditLogRepository } from '../../../src/infrastructure/persistence/repositories/audit-log.repository';
import { ApplicationError } from '../../../src/application/errors/application.error';

describe('GetBackupShareHandler', () => {
  let handler: GetBackupShareHandler;
  let mockRepository: any;
  let mockEncryptionService: any;
  let mockAuditLogRepository: any;

  const createMockShare = (status: BackupShareStatus = BackupShareStatus.ACTIVE) => {
    const share = BackupShare.create({
      userId: BigInt(12345),
      accountSequence: BigInt(1001),
      publicKey: '02' + 'a'.repeat(64),
      encryptedShareData: 'double-encrypted-data',
      encryptionKeyId: 'key-v1',
    });
    share.setShareId(BigInt(1));
    if (status === BackupShareStatus.REVOKED) {
      share.revoke('TEST');
    }
    return share;
  };

  beforeEach(async () => {
    mockRepository = {
      findByUserIdAndPublicKey: jest.fn(),
      save: jest.fn().mockImplementation((share) => Promise.resolve(share)),
    };

    mockEncryptionService = {
      decrypt: jest.fn().mockResolvedValue('original-encrypted-data'),
    };

    mockAuditLogRepository = {
      log: jest.fn().mockResolvedValue(undefined),
      countRetrievesByUserToday: jest.fn().mockResolvedValue(0),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        GetBackupShareHandler,
        { provide: BACKUP_SHARE_REPOSITORY, useValue: mockRepository },
        { provide: AesEncryptionService, useValue: mockEncryptionService },
        { provide: AuditLogRepository, useValue: mockAuditLogRepository },
        {
          provide: ConfigService,
          useValue: {
            get: (key: string) => {
              if (key === 'MAX_RETRIEVE_PER_DAY') return 3;
              return undefined;
            },
          },
        },
      ],
    }).compile();

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

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

  describe('execute', () => {
    const validQuery = new GetBackupShareQuery(
      '12345',
      '02' + 'a'.repeat(64),
      'valid-recovery-token',
      'identity-service',
      '127.0.0.1',
    );

    it('should retrieve backup share successfully', async () => {
      const mockShare = createMockShare();
      mockRepository.findByUserIdAndPublicKey.mockResolvedValue(mockShare);

      const result = await handler.execute(validQuery);

      expect(result.encryptedShareData).toBe('original-encrypted-data');
      expect(result.partyIndex).toBe(2);
      expect(result.publicKey).toBe(validQuery.publicKey);
      expect(mockEncryptionService.decrypt).toHaveBeenCalledWith(
        'double-encrypted-data',
        'key-v1',
      );
      expect(mockAuditLogRepository.log).toHaveBeenCalledWith(
        expect.objectContaining({
          action: 'RETRIEVE',
          success: true,
        }),
      );
    });

    it('should throw error if share not found', async () => {
      mockRepository.findByUserIdAndPublicKey.mockResolvedValue(null);

      await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError);
      await expect(handler.execute(validQuery)).rejects.toThrow('Backup share not found');
    });

    it('should throw error if share is not active', async () => {
      const revokedShare = createMockShare(BackupShareStatus.REVOKED);
      mockRepository.findByUserIdAndPublicKey.mockResolvedValue(revokedShare);

      await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError);
      await expect(handler.execute(validQuery)).rejects.toThrow('Backup share is not active');
    });

    it('should throw error if rate limit exceeded', async () => {
      mockAuditLogRepository.countRetrievesByUserToday.mockResolvedValue(3);

      await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError);
      await expect(handler.execute(validQuery)).rejects.toThrow('Rate limit exceeded');
    });

    it('should increment access count', async () => {
      const mockShare = createMockShare();
      const initialAccessCount = mockShare.accessCount;
      mockRepository.findByUserIdAndPublicKey.mockResolvedValue(mockShare);

      await handler.execute(validQuery);

      expect(mockShare.accessCount).toBe(initialAccessCount + 1);
      expect(mockRepository.save).toHaveBeenCalled();
    });
  });
});
