/**
 * DigitalSignatureService Unit Tests
 * Module: Documents (MAE-016)
 * GAP: GAP-006
 */

import { Repository, SelectQueryBuilder } from 'typeorm';
import { DigitalSignatureService, CreateSignatureRequestDto, SignDocumentDto } from '../services/digital-signature.service';
import { DigitalSignature } from '../entities/digital-signature.entity';

const createMockRepository = <T>(): jest.Mocked<Repository<T>> => ({
  create: jest.fn(),
  save: jest.fn(),
  findOne: jest.fn(),
  find: jest.fn(),
  count: jest.fn(),
  update: jest.fn(),
  createQueryBuilder: jest.fn(),
} as unknown as jest.Mocked<Repository<T>>);

const createMockQueryBuilder = <T>(): jest.Mocked<SelectQueryBuilder<T>> => ({
  where: jest.fn().mockReturnThis(),
  andWhere: jest.fn().mockReturnThis(),
  orderBy: jest.fn().mockReturnThis(),
  skip: jest.fn().mockReturnThis(),
  take: jest.fn().mockReturnThis(),
  getManyAndCount: jest.fn(),
} as unknown as jest.Mocked<SelectQueryBuilder<T>>);

describe('DigitalSignatureService', () => {
  let service: DigitalSignatureService;
  let repository: jest.Mocked<Repository<DigitalSignature>>;

  const mockTenantId = 'tenant-123';
  const mockUserId = 'user-456';

  const mockSignature: Partial<DigitalSignature> = {
    id: 'sig-1',
    tenantId: mockTenantId,
    documentId: 'doc-1',
    signerId: 'signer-1',
    signerName: 'Juan Perez',
    signerEmail: 'juan@example.com',
    signatureType: 'simple',
    status: 'pending',
    requestedAt: new Date('2026-02-01'),
    isValid: true,
  };

  beforeEach(() => {
    repository = createMockRepository<DigitalSignature>();

    const mockDataSource = {
      getRepository: jest.fn().mockReturnValue(repository),
    };

    service = new DigitalSignatureService(mockDataSource as any);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('requestSignature', () => {
    const createDto: CreateSignatureRequestDto = {
      documentId: 'doc-1',
      signerId: 'signer-1',
      signerName: 'Juan Perez',
      signerEmail: 'juan@example.com',
      signatureType: 'simple',
    };

    it('should create a signature request', async () => {
      repository.create.mockReturnValue(mockSignature as DigitalSignature);
      repository.save.mockResolvedValue(mockSignature as DigitalSignature);

      const result = await service.requestSignature(mockTenantId, createDto, mockUserId);

      expect(repository.create).toHaveBeenCalledWith(expect.objectContaining({
        tenantId: mockTenantId,
        documentId: createDto.documentId,
        signerId: createDto.signerId,
        status: 'pending',
      }));
      expect(result).toEqual(mockSignature);
    });
  });

  describe('signDocument', () => {
    const signDto: SignDocumentDto = {
      signatureData: 'base64-signature-data',
      ipAddress: '192.168.1.1',
      userAgent: 'Mozilla/5.0',
    };

    it('should sign a pending signature request', async () => {
      repository.findOne.mockResolvedValue(mockSignature as DigitalSignature);
      const signedSignature = { ...mockSignature, status: 'signed', signedAt: new Date() };
      repository.save.mockResolvedValue(signedSignature as DigitalSignature);

      const result = await service.signDocument(mockTenantId, 'sig-1', signDto, mockUserId);

      expect(repository.save).toHaveBeenCalledWith(expect.objectContaining({
        status: 'signed',
        signatureData: signDto.signatureData,
      }));
      expect(result?.status).toBe('signed');
    });

    it('should throw error if signature not pending', async () => {
      const signedSig = { ...mockSignature, status: 'signed' };
      repository.findOne.mockResolvedValue(signedSig as DigitalSignature);

      await expect(service.signDocument(mockTenantId, 'sig-1', signDto, mockUserId))
        .rejects.toThrow('Signature request is not pending');
    });

    it('should throw error if signature expired', async () => {
      const expiredSig = { ...mockSignature, expiresAt: new Date('2025-01-01') };
      repository.findOne.mockResolvedValue(expiredSig as DigitalSignature);
      repository.save.mockResolvedValue({ ...expiredSig, status: 'expired' } as DigitalSignature);

      await expect(service.signDocument(mockTenantId, 'sig-1', signDto, mockUserId))
        .rejects.toThrow('Signature request has expired');
    });
  });

  describe('rejectSignature', () => {
    it('should reject a pending signature', async () => {
      repository.findOne.mockResolvedValue(mockSignature as DigitalSignature);
      repository.save.mockResolvedValue({ ...mockSignature, status: 'rejected' } as DigitalSignature);

      const result = await service.rejectSignature(mockTenantId, 'sig-1', { reason: 'Not authorized' }, mockUserId);

      expect(result?.status).toBe('rejected');
    });
  });

  describe('revokeSignature', () => {
    it('should revoke a signed signature', async () => {
      const signedSig = { ...mockSignature, status: 'signed' };
      repository.findOne.mockResolvedValue(signedSig as DigitalSignature);
      repository.save.mockResolvedValue({ ...signedSig, status: 'revoked', isValid: false } as DigitalSignature);

      const result = await service.revokeSignature(mockTenantId, 'sig-1', 'Document superseded', mockUserId);

      expect(result?.status).toBe('revoked');
      expect(result?.isValid).toBe(false);
    });

    it('should throw error if not signed', async () => {
      repository.findOne.mockResolvedValue(mockSignature as DigitalSignature);

      await expect(service.revokeSignature(mockTenantId, 'sig-1', 'Reason', mockUserId))
        .rejects.toThrow('Only signed documents can be revoked');
    });
  });

  describe('validateSignature', () => {
    it('should validate a signed signature', async () => {
      const signedSig = { ...mockSignature, status: 'signed' };
      repository.findOne.mockResolvedValue(signedSig as DigitalSignature);
      repository.save.mockResolvedValue(signedSig as DigitalSignature);

      const result = await service.validateSignature(mockTenantId, 'sig-1');

      expect(result.isValid).toBe(true);
      expect(result.errors).toBeUndefined();
    });

    it('should return errors for invalid status', async () => {
      repository.findOne.mockResolvedValue(mockSignature as DigitalSignature); // pending
      repository.save.mockResolvedValue(mockSignature as DigitalSignature);

      const result = await service.validateSignature(mockTenantId, 'sig-1');

      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Invalid status: pending');
    });
  });

  describe('getStatistics', () => {
    it('should return signature statistics', async () => {
      repository.count.mockResolvedValueOnce(10) // pending
        .mockResolvedValueOnce(50) // signed
        .mockResolvedValueOnce(5)  // rejected
        .mockResolvedValueOnce(3)  // expired
        .mockResolvedValueOnce(2); // revoked

      const stats = await service.getStatistics(mockTenantId);

      expect(stats).toEqual({
        pending: 10,
        signed: 50,
        rejected: 5,
        expired: 3,
        revoked: 2,
      });
    });
  });
});
