import { Test, TestingModule } from '@nestjs/testing';
import { BackupShareRepositoryImpl } from '../../src/infrastructure/persistence/repositories/backup-share.repository.impl';
import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service';
import { MockPrismaService } from '../utils/mock-prisma.service';
import { BackupShare, BackupShareStatus } from '../../src/domain';

describe('BackupShareRepository Integration', () => {
  let repository: BackupShareRepositoryImpl;
  let mockPrisma: MockPrismaService;

  beforeEach(async () => {
    mockPrisma = new MockPrismaService();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        BackupShareRepositoryImpl,
        { provide: PrismaService, useValue: mockPrisma },
      ],
    }).compile();

    repository = module.get<BackupShareRepositoryImpl>(BackupShareRepositoryImpl);
  });

  afterEach(() => {
    mockPrisma.reset();
  });

  describe('save', () => {
    it('should create a new backup share', async () => {
      const share = BackupShare.create({
        userId: BigInt(12345),
        accountSequence: BigInt(1001),
        publicKey: '02' + 'a'.repeat(64),
        encryptedShareData: 'encrypted-data',
        encryptionKeyId: 'key-v1',
      });

      const saved = await repository.save(share);

      expect(saved.shareId).toBeDefined();
      expect(saved.userId).toBe(BigInt(12345));
      expect(saved.publicKey).toBe('02' + 'a'.repeat(64));
      expect(mockPrisma.backupShare.create).toHaveBeenCalled();
    });

    it('should update an existing backup share', async () => {
      // First create
      const share = BackupShare.create({
        userId: BigInt(12345),
        accountSequence: BigInt(1001),
        publicKey: '02' + 'a'.repeat(64),
        encryptedShareData: 'encrypted-data',
        encryptionKeyId: 'key-v1',
      });

      const saved = await repository.save(share);

      // Modify and update
      saved.recordAccess();
      const updated = await repository.save(saved);

      expect(updated.accessCount).toBe(1);
      expect(mockPrisma.backupShare.update).toHaveBeenCalled();
    });

    it('should preserve all fields when saving', async () => {
      const share = BackupShare.create({
        userId: BigInt(99999),
        accountSequence: BigInt(9999),
        publicKey: '02' + 'b'.repeat(64),
        encryptedShareData: 'test-encrypted-data',
        encryptionKeyId: 'key-v2',
        threshold: 3,
        totalParties: 5,
      });

      const saved = await repository.save(share);

      expect(saved.threshold).toBe(3);
      expect(saved.totalParties).toBe(5);
      expect(saved.partyIndex).toBe(2);
      expect(saved.status).toBe(BackupShareStatus.ACTIVE);
    });
  });

  describe('findByUserId', () => {
    it('should find share by user ID', async () => {
      const share = BackupShare.create({
        userId: BigInt(55555),
        accountSequence: BigInt(5555),
        publicKey: '02' + 'c'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      await repository.save(share);

      const found = await repository.findByUserId(BigInt(55555));

      expect(found).toBeDefined();
      expect(found?.userId).toBe(BigInt(55555));
    });

    it('should return null for non-existent user', async () => {
      const found = await repository.findByUserId(BigInt(99999999));

      expect(found).toBeNull();
    });
  });

  describe('findByPublicKey', () => {
    it('should find share by public key', async () => {
      const publicKey = '02' + 'd'.repeat(64);
      const share = BackupShare.create({
        userId: BigInt(66666),
        accountSequence: BigInt(6666),
        publicKey,
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      await repository.save(share);

      const found = await repository.findByPublicKey(publicKey);

      expect(found).toBeDefined();
      expect(found?.publicKey).toBe(publicKey);
    });
  });

  describe('findByUserIdAndPublicKey', () => {
    it('should find share by user ID and public key combination', async () => {
      const userId = BigInt(77777);
      const publicKey = '02' + 'e'.repeat(64);
      const share = BackupShare.create({
        userId,
        accountSequence: BigInt(7777),
        publicKey,
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      await repository.save(share);

      const found = await repository.findByUserIdAndPublicKey(userId, publicKey);

      expect(found).toBeDefined();
      expect(found?.userId).toBe(userId);
      expect(found?.publicKey).toBe(publicKey);
    });

    it('should return null when user ID matches but public key does not', async () => {
      const share = BackupShare.create({
        userId: BigInt(88888),
        accountSequence: BigInt(8888),
        publicKey: '02' + 'f'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      await repository.save(share);

      const found = await repository.findByUserIdAndPublicKey(
        BigInt(88888),
        '02' + 'g'.repeat(64), // Different public key
      );

      expect(found).toBeNull();
    });
  });

  describe('findByAccountSequence', () => {
    it('should find share by account sequence', async () => {
      const accountSequence = BigInt(123456);
      const share = BackupShare.create({
        userId: BigInt(11111),
        accountSequence,
        publicKey: '02' + 'h'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      await repository.save(share);

      const found = await repository.findByAccountSequence(accountSequence);

      expect(found).toBeDefined();
      expect(found?.accountSequence).toBe(accountSequence);
    });
  });

  describe('delete', () => {
    it('should delete a backup share', async () => {
      const share = BackupShare.create({
        userId: BigInt(22222),
        accountSequence: BigInt(2222),
        publicKey: '02' + 'i'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      const saved = await repository.save(share);

      await repository.delete(saved.shareId!);

      expect(mockPrisma.backupShare.delete).toHaveBeenCalledWith({
        where: { shareId: saved.shareId },
      });
    });
  });

  describe('entity state transitions', () => {
    it('should persist revoked status', async () => {
      const share = BackupShare.create({
        userId: BigInt(33333),
        accountSequence: BigInt(3333),
        publicKey: '02' + 'j'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      const saved = await repository.save(share);

      saved.revoke('SECURITY_BREACH');
      await repository.save(saved);

      expect(mockPrisma.backupShare.update).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            status: 'REVOKED',
          }),
        }),
      );
    });

    it('should persist access count updates', async () => {
      const share = BackupShare.create({
        userId: BigInt(44444),
        accountSequence: BigInt(4444),
        publicKey: '02' + 'k'.repeat(64),
        encryptedShareData: 'data',
        encryptionKeyId: 'key-v1',
      });
      const saved = await repository.save(share);

      saved.recordAccess();
      saved.recordAccess();
      await repository.save(saved);

      expect(mockPrisma.backupShare.update).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            accessCount: 2,
          }),
        }),
      );
    });
  });
});
