import { Test, TestingModule } from '@nestjs/testing';
import { BackupShareController } from '../../../src/api/controllers/backup-share.controller';
import { BackupShareApplicationService } from '../../../src/application/services/backup-share-application.service';
import { ServiceAuthGuard } from '../../../src/shared/guards/service-auth.guard';
import { ConfigService } from '@nestjs/config';
import {
  createStoreSharePayload,
  createRetrieveSharePayload,
  createRevokeSharePayload,
  generatePublicKey,
} from '../../utils/test-utils';

describe('BackupShareController', () => {
  let controller: BackupShareController;
  let mockApplicationService: jest.Mocked<BackupShareApplicationService>;

  const mockRequest = {
    sourceService: 'identity-service',
    sourceIp: '127.0.0.1',
  };

  beforeEach(async () => {
    mockApplicationService = {
      storeBackupShare: jest.fn(),
      getBackupShare: jest.fn(),
      revokeShare: jest.fn(),
    } as any;

    const module: TestingModule = await Test.createTestingModule({
      controllers: [BackupShareController],
      providers: [
        { provide: BackupShareApplicationService, useValue: mockApplicationService },
        {
          provide: ConfigService,
          useValue: {
            get: jest.fn().mockReturnValue('test-secret'),
          },
        },
      ],
    })
      .overrideGuard(ServiceAuthGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<BackupShareController>(BackupShareController);
  });

  describe('storeShare', () => {
    it('should store a backup share successfully', async () => {
      const dto = createStoreSharePayload();
      mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '1' });

      const result = await controller.storeShare(dto, mockRequest);

      expect(result.success).toBe(true);
      expect(result.shareId).toBe('1');
      expect(result.message).toBe('Backup share stored successfully');
      expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: dto.userId,
          accountSequence: dto.accountSequence,
          publicKey: dto.publicKey,
          encryptedShareData: dto.encryptedShareData,
          sourceService: 'identity-service',
          sourceIp: '127.0.0.1',
        }),
      );
    });

    it('should pass optional threshold and totalParties', async () => {
      const dto = createStoreSharePayload({
        threshold: 3,
        totalParties: 5,
      });
      mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '2' });

      await controller.storeShare(dto, mockRequest);

      expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          threshold: 3,
          totalParties: 5,
        }),
      );
    });

    it('should handle different user IDs', async () => {
      const dto = createStoreSharePayload({ userId: '999888777' });
      mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '3' });

      await controller.storeShare(dto, mockRequest);

      expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: '999888777',
        }),
      );
    });
  });

  describe('retrieveShare', () => {
    it('should retrieve a backup share successfully', async () => {
      const dto = createRetrieveSharePayload();
      mockApplicationService.getBackupShare.mockResolvedValue({
        encryptedShareData: 'decrypted-data',
        partyIndex: 2,
        publicKey: dto.publicKey,
      });

      const result = await controller.retrieveShare(dto, mockRequest);

      expect(result.success).toBe(true);
      expect(result.encryptedShareData).toBe('decrypted-data');
      expect(result.partyIndex).toBe(2);
      expect(result.publicKey).toBe(dto.publicKey);
    });

    it('should pass deviceId when provided', async () => {
      const dto = createRetrieveSharePayload({ deviceId: 'device-123' });
      mockApplicationService.getBackupShare.mockResolvedValue({
        encryptedShareData: 'data',
        partyIndex: 2,
        publicKey: dto.publicKey,
      });

      await controller.retrieveShare(dto, mockRequest);

      expect(mockApplicationService.getBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          deviceId: 'device-123',
        }),
      );
    });

    it('should include recovery token in query', async () => {
      const dto = createRetrieveSharePayload({ recoveryToken: 'special-token-123' });
      mockApplicationService.getBackupShare.mockResolvedValue({
        encryptedShareData: 'data',
        partyIndex: 2,
        publicKey: dto.publicKey,
      });

      await controller.retrieveShare(dto, mockRequest);

      expect(mockApplicationService.getBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          recoveryToken: 'special-token-123',
        }),
      );
    });
  });

  describe('revokeShare', () => {
    it('should revoke a backup share successfully', async () => {
      const dto = createRevokeSharePayload();
      mockApplicationService.revokeShare.mockResolvedValue(undefined);

      const result = await controller.revokeShare(dto, mockRequest);

      expect(result.success).toBe(true);
      expect(result.message).toBe('Backup share revoked successfully');
    });

    it('should pass correct reason to service', async () => {
      const dto = createRevokeSharePayload({ reason: 'SECURITY_BREACH' });
      mockApplicationService.revokeShare.mockResolvedValue(undefined);

      await controller.revokeShare(dto, mockRequest);

      expect(mockApplicationService.revokeShare).toHaveBeenCalledWith(
        expect.objectContaining({
          reason: 'SECURITY_BREACH',
        }),
      );
    });

    it('should handle different revoke reasons', async () => {
      const reasons = ['ROTATION', 'ACCOUNT_CLOSED', 'SECURITY_BREACH', 'USER_REQUEST'];

      for (const reason of reasons) {
        const dto = createRevokeSharePayload({
          reason,
          publicKey: generatePublicKey(reason[0].toLowerCase()),
        });
        mockApplicationService.revokeShare.mockResolvedValue(undefined);

        await controller.revokeShare(dto, mockRequest);

        expect(mockApplicationService.revokeShare).toHaveBeenCalledWith(
          expect.objectContaining({ reason }),
        );
      }
    });
  });

  describe('request context', () => {
    it('should extract source service from request', async () => {
      const dto = createStoreSharePayload();
      const customRequest = { sourceService: 'recovery-service', sourceIp: '10.0.0.1' };
      mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '1' });

      await controller.storeShare(dto, customRequest);

      expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith(
        expect.objectContaining({
          sourceService: 'recovery-service',
          sourceIp: '10.0.0.1',
        }),
      );
    });
  });
});
