import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ComplianceService } from './compliance.service';
import { User } from '../../database/entities/user.entity';
import { DeletionRequest } from '../../database/entities/deletion-request.entity';
import { FamilyMember } from '../../database/entities/family-member.entity';
import { Child } from '../../database/entities/child.entity';
import { Activity } from '../../database/entities/activity.entity';
import { AIConversation } from '../../database/entities/ai-conversation.entity';
import { Photo } from '../../database/entities/photo.entity';
import { AuditLog } from '../../database/entities/audit-log.entity';
import { NotFoundException } from '@nestjs/common';

describe('ComplianceService', () => {
  let service: ComplianceService;
  let userRepository: Repository<User>;
  let deletionRequestRepository: Repository<DeletionRequest>;
  let auditLogRepository: Repository<AuditLog>;

  const mockUser = {
    id: 'user_123',
    name: 'John Doe',
    email: 'john@example.com',
    dateOfBirth: new Date('1990-01-01'),
    createdAt: new Date('2024-01-01'),
    emailVerified: true,
  };

  const mockUserRepository = {
    findOne: jest.fn(),
    createQueryBuilder: jest.fn(),
  };

  const mockDeletionRequestRepository = {
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
  };

  const mockAuditLogRepository = {
    create: jest.fn(),
    save: jest.fn(),
  };

  const mockFamilyMemberRepository = {
    find: jest.fn(),
  };

  const mockChildRepository = {
    find: jest.fn(),
  };

  const mockActivityRepository = {
    find: jest.fn(),
  };

  const mockAIConversationRepository = {
    find: jest.fn(),
  };

  const mockPhotoRepository = {
    find: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ComplianceService,
        {
          provide: getRepositoryToken(User),
          useValue: mockUserRepository,
        },
        {
          provide: getRepositoryToken(DeletionRequest),
          useValue: mockDeletionRequestRepository,
        },
        {
          provide: getRepositoryToken(FamilyMember),
          useValue: mockFamilyMemberRepository,
        },
        {
          provide: getRepositoryToken(Child),
          useValue: mockChildRepository,
        },
        {
          provide: getRepositoryToken(Activity),
          useValue: mockActivityRepository,
        },
        {
          provide: getRepositoryToken(AIConversation),
          useValue: mockAIConversationRepository,
        },
        {
          provide: getRepositoryToken(Photo),
          useValue: mockPhotoRepository,
        },
        {
          provide: getRepositoryToken(AuditLog),
          useValue: mockAuditLogRepository,
        },
      ],
    }).compile();

    service = module.get<ComplianceService>(ComplianceService);
    userRepository = module.get<Repository<User>>(getRepositoryToken(User));
    deletionRequestRepository = module.get<Repository<DeletionRequest>>(
      getRepositoryToken(DeletionRequest),
    );
    auditLogRepository = module.get<Repository<AuditLog>>(
      getRepositoryToken(AuditLog),
    );

    // Reset mocks
    jest.clearAllMocks();
  });

  describe('exportUserData', () => {
    it('should export all user data successfully', async () => {
      const mockFamilies = [
        { id: 'fam_1', name: 'Doe Family', role: 'admin' },
      ];
      const mockChildren = [
        {
          id: 'child_1',
          name: 'Jane Doe',
          dateOfBirth: new Date('2020-01-01'),
          gender: 'female',
        },
      ];
      const mockActivities = [
        {
          id: 'act_1',
          type: 'feeding',
          timestamp: new Date(),
          data: { amount: 120, unit: 'ml' },
        },
      ];
      const mockConversations = [
        {
          id: 'conv_1',
          createdAt: new Date(),
          messages: [
            {
              role: 'user',
              content: 'How much should my baby eat?',
              timestamp: new Date(),
            },
          ],
        },
      ];

      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockFamilyMemberRepository.find.mockResolvedValue(mockFamilies);
      mockChildRepository.find.mockResolvedValue(mockChildren);
      mockActivityRepository.find.mockResolvedValue(mockActivities);
      mockAIConversationRepository.find.mockResolvedValue(mockConversations);

      const result = await service.exportUserData('user_123');

      expect(result).toHaveProperty('user');
      expect(result).toHaveProperty('families');
      expect(result).toHaveProperty('children');
      expect(result).toHaveProperty('activities');
      expect(result).toHaveProperty('aiConversations');

      expect(result.user.id).toBe('user_123');
      expect(result.user.email).toBe('john@example.com');
      expect(result.families).toHaveLength(1);
      expect(result.children).toHaveLength(1);
      expect(result.activities).toHaveLength(1);
      expect(result.aiConversations).toHaveLength(1);
    });

    it('should throw NotFoundException when user does not exist', async () => {
      mockUserRepository.findOne.mockResolvedValue(null);

      await expect(service.exportUserData('nonexistent_user')).rejects.toThrow(
        NotFoundException,
      );
    });

    it('should handle user with no data', async () => {
      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockFamilyMemberRepository.find.mockResolvedValue([]);
      mockChildRepository.find.mockResolvedValue([]);
      mockActivityRepository.find.mockResolvedValue([]);
      mockAIConversationRepository.find.mockResolvedValue([]);

      const result = await service.exportUserData('user_123');

      expect(result.families).toEqual([]);
      expect(result.children).toEqual([]);
      expect(result.activities).toEqual([]);
      expect(result.aiConversations).toEqual([]);
    });
  });

  describe('requestAccountDeletion', () => {
    it('should schedule account deletion with 30-day grace period', async () => {
      const mockDeletionRequest = {
        id: 'del_123',
        userId: 'user_123',
        requestedAt: new Date(),
        scheduledDeletionAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
        status: 'pending',
        reason: 'Testing',
        ipAddress: '127.0.0.1',
        userAgent: 'Test Agent',
      };

      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockDeletionRequestRepository.create.mockReturnValue(mockDeletionRequest);
      mockDeletionRequestRepository.save.mockResolvedValue(mockDeletionRequest);

      const result = await service.requestAccountDeletion(
        'user_123',
        'Testing',
        '127.0.0.1',
        'Test Agent',
      );

      expect(result.userId).toBe('user_123');
      expect(result.status).toBe('pending');
      expect(result.reason).toBe('Testing');

      // Verify scheduled deletion is ~30 days from now
      const daysDiff = Math.floor(
        (result.scheduledDeletionAt.getTime() - Date.now()) /
          (1000 * 60 * 60 * 24),
      );
      expect(daysDiff).toBeGreaterThanOrEqual(29);
      expect(daysDiff).toBeLessThanOrEqual(30);
    });

    it('should throw error if deletion request already exists', async () => {
      const existingRequest = {
        id: 'del_existing',
        userId: 'user_123',
        status: 'pending',
      };

      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockDeletionRequestRepository.findOne.mockResolvedValue(
        existingRequest,
      );

      await expect(
        service.requestAccountDeletion(
          'user_123',
          'Testing',
          '127.0.0.1',
          'Test Agent',
        ),
      ).rejects.toThrow('Deletion request already pending');
    });

    it('should create audit log for deletion request', async () => {
      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockDeletionRequestRepository.create.mockReturnValue({});
      mockDeletionRequestRepository.save.mockResolvedValue({});
      mockAuditLogRepository.create.mockReturnValue({});
      mockAuditLogRepository.save.mockResolvedValue({});

      await service.requestAccountDeletion(
        'user_123',
        'Testing',
        '127.0.0.1',
        'Test Agent',
      );

      expect(mockAuditLogRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'user_123',
          action: 'account_deletion_requested',
          ipAddress: '127.0.0.1',
        }),
      );
      expect(mockAuditLogRepository.save).toHaveBeenCalled();
    });
  });

  describe('cancelAccountDeletion', () => {
    it('should cancel pending deletion request', async () => {
      const mockDeletionRequest = {
        id: 'del_123',
        userId: 'user_123',
        status: 'pending',
        requestedAt: new Date(),
        scheduledDeletionAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
      };

      mockDeletionRequestRepository.findOne.mockResolvedValue(
        mockDeletionRequest,
      );
      mockDeletionRequestRepository.save.mockResolvedValue({
        ...mockDeletionRequest,
        status: 'cancelled',
        cancelledAt: new Date(),
      });

      const result = await service.cancelAccountDeletion(
        'user_123',
        'Changed my mind',
      );

      expect(result.status).toBe('cancelled');
      expect(result.cancelledAt).toBeDefined();
      expect(mockDeletionRequestRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          status: 'cancelled',
          cancellationReason: 'Changed my mind',
        }),
      );
    });

    it('should throw NotFoundException when no pending request exists', async () => {
      mockDeletionRequestRepository.findOne.mockResolvedValue(null);

      await expect(
        service.cancelAccountDeletion('user_123', 'Changed my mind'),
      ).rejects.toThrow(NotFoundException);
    });
  });

  describe('getPendingDeletionRequest', () => {
    it('should return pending deletion request', async () => {
      const mockDeletionRequest = {
        id: 'del_123',
        userId: 'user_123',
        status: 'pending',
        requestedAt: new Date(),
        scheduledDeletionAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
        reason: 'Testing',
      };

      mockDeletionRequestRepository.findOne.mockResolvedValue(
        mockDeletionRequest,
      );

      const result = await service.getPendingDeletionRequest('user_123');

      expect(result).toEqual(mockDeletionRequest);
      expect(mockDeletionRequestRepository.findOne).toHaveBeenCalledWith({
        where: { userId: 'user_123', status: 'pending' },
      });
    });

    it('should return null when no pending request exists', async () => {
      mockDeletionRequestRepository.findOne.mockResolvedValue(null);

      const result = await service.getPendingDeletionRequest('user_123');

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