/**
 * PartyShare Repository Integration Tests
 */

import { Test, TestingModule } from '@nestjs/testing';
import { PartyShareRepositoryImpl } from '../../src/infrastructure/persistence/repositories/party-share.repository.impl';
import { PartyShareMapper } from '../../src/infrastructure/persistence/mappers/party-share.mapper';
import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service';
import { PartyShare } from '../../src/domain/entities/party-share.entity';
import {
  ShareId,
  PartyId,
  SessionId,
  Threshold,
  ShareData,
  PublicKey,
} from '../../src/domain/value-objects';
import { PartyShareType, PartyShareStatus } from '../../src/domain/enums';

describe('PartyShareRepository (Integration)', () => {
  let repository: PartyShareRepositoryImpl;
  let prismaService: any;
  let mapper: PartyShareMapper;

  const createMockShare = (): PartyShare => {
    return PartyShare.create({
      partyId: PartyId.create('user123-server'),
      sessionId: SessionId.generate(),
      shareType: PartyShareType.WALLET,
      shareData: ShareData.create(
        Buffer.from('encrypted-test-share-data'),
        Buffer.from('123456789012'),
        Buffer.from('1234567890123456'),
      ),
      publicKey: PublicKey.fromHex('03' + '0'.repeat(64)),
      threshold: Threshold.create(3, 2),
    });
  };

  beforeEach(async () => {
    prismaService = {
      partyShare: {
        findUnique: jest.fn(),
        findFirst: jest.fn(),
        findMany: jest.fn(),
        create: jest.fn(),
        update: jest.fn(),
        delete: jest.fn(),
        count: jest.fn(),
      },
      $transaction: jest.fn((fn: any) => fn(prismaService)),
    };

    mapper = new PartyShareMapper();

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

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

  describe('save', () => {
    it('should save a new share', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.create.mockResolvedValue({
        ...persistenceData,
        id: share.id.value,
      });

      await repository.save(share);

      expect(prismaService.partyShare.create).toHaveBeenCalled();
    });
  });

  describe('update', () => {
    it('should update an existing share', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.update.mockResolvedValue({
        ...persistenceData,
        id: share.id.value,
      });

      await repository.update(share);

      expect(prismaService.partyShare.update).toHaveBeenCalled();
    });
  });

  describe('findById', () => {
    it('should return share when found', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.findUnique.mockResolvedValue({
        ...persistenceData,
        id: share.id.value,
      });

      const result = await repository.findById(share.id);

      expect(result).toBeDefined();
      expect(result).toBeInstanceOf(PartyShare);
    });

    it('should return null when not found', async () => {
      prismaService.partyShare.findUnique.mockResolvedValue(null);

      const result = await repository.findById(ShareId.generate());

      expect(result).toBeNull();
    });
  });

  describe('findByPartyId', () => {
    it('should return all shares for party', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.findMany.mockResolvedValue([
        { ...persistenceData, id: share.id.value },
      ]);

      const result = await repository.findByPartyId(PartyId.create('user123-server'));

      expect(result).toHaveLength(1);
      result.forEach((s) => {
        expect(s).toBeInstanceOf(PartyShare);
      });
    });

    it('should return empty array when no shares', async () => {
      prismaService.partyShare.findMany.mockResolvedValue([]);

      const result = await repository.findByPartyId(PartyId.create('nonexistent-party'));

      expect(result).toHaveLength(0);
    });
  });

  describe('findByPublicKey', () => {
    it('should return share when found', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.findFirst.mockResolvedValue({
        ...persistenceData,
        id: share.id.value,
      });

      const result = await repository.findByPublicKey(
        PublicKey.fromHex('03' + '0'.repeat(64)),
      );

      expect(result).toBeDefined();
      expect(result).toBeInstanceOf(PartyShare);
    });

    it('should return null when not found', async () => {
      prismaService.partyShare.findFirst.mockResolvedValue(null);

      const result = await repository.findByPublicKey(
        PublicKey.fromHex('03' + '1'.repeat(64)),
      );

      expect(result).toBeNull();
    });
  });

  describe('findBySessionId', () => {
    it('should return shares for session', async () => {
      const share = createMockShare();
      const persistenceData = mapper.toPersistence(share);

      prismaService.partyShare.findMany.mockResolvedValue([
        { ...persistenceData, id: share.id.value },
      ]);

      const result = await repository.findBySessionId(share.sessionId);

      expect(result).toHaveLength(1);
    });
  });

  describe('delete', () => {
    it('should soft delete share by updating status to revoked', async () => {
      const shareId = ShareId.generate();

      prismaService.partyShare.update.mockResolvedValue({});

      await repository.delete(shareId);

      expect(prismaService.partyShare.update).toHaveBeenCalledWith({
        where: { id: shareId.value },
        data: {
          status: PartyShareStatus.REVOKED,
          updatedAt: expect.any(Date),
        },
      });
    });
  });

});
