import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere, In } from 'typeorm';
import { NotFoundException, BadRequestException } from '@nestjs/common';
import { FeedbackService, FeedbackFilters } from './feedback.service';
import {
  Feedback,
  FeedbackType,
  FeedbackStatus,
  FeedbackPriority,
} from './feedback.entity';
import { CreateFeedbackDto } from './dto/create-feedback.dto';
import {
  AnalyticsService,
  AnalyticsEvent,
} from '../../common/services/analytics.service';

describe('FeedbackService', () => {
  let service: FeedbackService;
  let feedbackRepository: Repository<Feedback>;
  let analyticsService: AnalyticsService;

  const mockFeedback = {
    id: 'fb_123',
    userId: 'user_123',
    type: FeedbackType.BUG_REPORT,
    title: 'App crashes',
    message: 'The app crashes when I try to log feeding',
    category: 'tracking',
    page: '/track/feeding',
    platform: 'web',
    appVersion: '1.0.0',
    sentiment: 'negative',
    priority: FeedbackPriority.HIGH,
    status: FeedbackStatus.NEW,
    upvotes: 0,
    contactAllowed: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockFeedbackRepository = {
    create: jest.fn(),
    save: jest.fn(),
    find: jest.fn(),
    findOne: jest.fn(),
    findAndCount: jest.fn(),
  };

  const mockAnalyticsService = {
    trackEvent: jest.fn().mockResolvedValue(undefined),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        FeedbackService,
        {
          provide: getRepositoryToken(Feedback),
          useValue: mockFeedbackRepository,
        },
        {
          provide: AnalyticsService,
          useValue: mockAnalyticsService,
        },
      ],
    }).compile();

    service = module.get<FeedbackService>(FeedbackService);
    feedbackRepository = module.get<Repository<Feedback>>(
      getRepositoryToken(Feedback),
    );
    analyticsService = module.get<AnalyticsService>(AnalyticsService);

    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('createFeedback', () => {
    it('should create feedback with sentiment detection', async () => {
      const createDto: CreateFeedbackDto = {
        type: FeedbackType.BUG_REPORT,
        message: 'App crashes and has terrible bugs',
        title: 'Crashes',
        category: 'tracking',
        page: '/track',
        platform: 'web',
      };

      mockFeedbackRepository.create.mockReturnValue(mockFeedback);
      mockFeedbackRepository.save.mockResolvedValue(mockFeedback);

      const result = await service.createFeedback('user_123', createDto);

      expect(feedbackRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'user_123',
          type: FeedbackType.BUG_REPORT,
          sentiment: 'negative',
          priority: FeedbackPriority.HIGH,
          status: FeedbackStatus.NEW,
        }),
      );
      expect(feedbackRepository.save).toHaveBeenCalled();
      expect(analyticsService.trackEvent).toHaveBeenCalledWith(
        expect.objectContaining({
          event: AnalyticsEvent.FEEDBACK_SUBMITTED,
          userId: 'user_123',
        }),
      );
      expect(result).toEqual(mockFeedback);
    });

    it('should assign HIGH priority to critical bugs', async () => {
      const createDto: CreateFeedbackDto = {
        type: FeedbackType.BUG_REPORT,
        message: 'Crash error awful',
        category: 'tracking',
      };

      mockFeedbackRepository.create.mockImplementation((data) => ({
        ...mockFeedback,
        ...data,
      }));
      mockFeedbackRepository.save.mockResolvedValue(mockFeedback);

      await service.createFeedback('user_123', createDto);

      expect(feedbackRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          priority: FeedbackPriority.HIGH,
        }),
      );
    });

    it('should assign HIGH priority to performance issues', async () => {
      const createDto: CreateFeedbackDto = {
        type: FeedbackType.PERFORMANCE_ISSUE,
        message: 'App is very slow',
        category: 'general',
      };

      mockFeedbackRepository.create.mockImplementation((data) => ({
        ...mockFeedback,
        ...data,
      }));
      mockFeedbackRepository.save.mockResolvedValue(mockFeedback);

      await service.createFeedback('user_123', createDto);

      expect(feedbackRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          priority: FeedbackPriority.HIGH,
        }),
      );
    });

    it('should assign MEDIUM/LOW priority to feature requests', async () => {
      const createDto: CreateFeedbackDto = {
        type: FeedbackType.FEATURE_REQUEST,
        message: 'Please add dark mode',
        category: 'ui',
      };

      mockFeedbackRepository.create.mockImplementation((data) => ({
        ...mockFeedback,
        ...data,
      }));
      mockFeedbackRepository.save.mockResolvedValue(mockFeedback);

      await service.createFeedback('user_123', createDto);

      const createCall = mockFeedbackRepository.create.mock.calls[0][0];
      expect([FeedbackPriority.MEDIUM, FeedbackPriority.LOW]).toContain(
        createCall.priority,
      );
    });

    it('should handle save failures', async () => {
      mockFeedbackRepository.save.mockRejectedValue(
        new Error('Database error'),
      );

      const createDto: CreateFeedbackDto = {
        type: FeedbackType.BUG_REPORT,
        message: 'Test',
        category: 'test',
      };

      await expect(
        service.createFeedback('user_123', createDto),
      ).rejects.toThrow(BadRequestException);
    });
  });

  describe('getFeedback', () => {
    it('should get feedback by ID', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(mockFeedback);

      const result = await service.getFeedback('fb_123');

      expect(feedbackRepository.findOne).toHaveBeenCalledWith({
        where: { id: 'fb_123' },
      });
      expect(result).toEqual(mockFeedback);
    });

    it('should throw NotFoundException if not found', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(null);

      await expect(service.getFeedback('fb_123')).rejects.toThrow(
        NotFoundException,
      );
    });
  });

  describe('getUserFeedback', () => {
    it('should get all feedback for a user', async () => {
      mockFeedbackRepository.find.mockResolvedValue([mockFeedback]);

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

      expect(feedbackRepository.find).toHaveBeenCalledWith({
        where: { userId: 'user_123' },
        order: { createdAt: 'DESC' },
      });
      expect(result).toEqual([mockFeedback]);
    });

    it('should filter by type', async () => {
      mockFeedbackRepository.find.mockResolvedValue([]);

      const filters: FeedbackFilters = {
        type: FeedbackType.FEATURE_REQUEST,
      };

      await service.getUserFeedback('user_123', filters);

      expect(feedbackRepository.find).toHaveBeenCalledWith({
        where: {
          userId: 'user_123',
          type: FeedbackType.FEATURE_REQUEST,
        },
        order: { createdAt: 'DESC' },
      });
    });

    it('should filter by status', async () => {
      mockFeedbackRepository.find.mockResolvedValue([]);

      const filters: FeedbackFilters = {
        status: FeedbackStatus.RESOLVED,
      };

      await service.getUserFeedback('user_123', filters);

      expect(feedbackRepository.find).toHaveBeenCalledWith({
        where: {
          userId: 'user_123',
          status: FeedbackStatus.RESOLVED,
        },
        order: { createdAt: 'DESC' },
      });
    });

    it('should filter by category', async () => {
      mockFeedbackRepository.find.mockResolvedValue([]);

      const filters: FeedbackFilters = {
        category: 'tracking',
      };

      await service.getUserFeedback('user_123', filters);

      expect(feedbackRepository.find).toHaveBeenCalledWith({
        where: {
          userId: 'user_123',
          category: 'tracking',
        },
        order: { createdAt: 'DESC' },
      });
    });
  });

  describe('getAllFeedback', () => {
    it('should get paginated feedback', async () => {
      mockFeedbackRepository.findAndCount.mockResolvedValue([
        [mockFeedback],
        10,
      ]);

      const result = await service.getAllFeedback({}, 2, 5);

      expect(feedbackRepository.findAndCount).toHaveBeenCalledWith({
        where: {},
        order: { priority: 'DESC', createdAt: 'DESC' },
        skip: 5,
        take: 5,
      });
      expect(result.feedback).toEqual([mockFeedback]);
      expect(result.total).toBe(10);
    });

    it('should apply all filters', async () => {
      mockFeedbackRepository.findAndCount.mockResolvedValue([[], 0]);

      const filters: FeedbackFilters = {
        type: FeedbackType.BUG_REPORT,
        status: FeedbackStatus.NEW,
        priority: FeedbackPriority.HIGH,
        category: 'tracking',
        platform: 'mobile',
      };

      await service.getAllFeedback(filters, 1, 10);

      expect(feedbackRepository.findAndCount).toHaveBeenCalledWith({
        where: {
          type: FeedbackType.BUG_REPORT,
          status: FeedbackStatus.NEW,
          priority: FeedbackPriority.HIGH,
          category: 'tracking',
          platform: 'mobile',
        },
        order: { priority: 'DESC', createdAt: 'DESC' },
        skip: 0,
        take: 10,
      });
    });
  });

  describe('updateStatus', () => {
    it('should update feedback status', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(mockFeedback);
      const updatedFeedback = { ...mockFeedback, status: FeedbackStatus.TRIAGED };
      mockFeedbackRepository.save.mockResolvedValue(updatedFeedback);

      const result = await service.updateStatus(
        'fb_123',
        FeedbackStatus.TRIAGED,
        'admin_123',
      );

      expect(feedbackRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          status: FeedbackStatus.TRIAGED,
        }),
      );
      expect(result.status).toBe(FeedbackStatus.TRIAGED);
    });

    it('should set resolution details when resolving', async () => {
      const feedbackToResolve = { ...mockFeedback };
      mockFeedbackRepository.findOne.mockResolvedValue(feedbackToResolve);
      mockFeedbackRepository.save.mockResolvedValue({
        ...feedbackToResolve,
        status: FeedbackStatus.RESOLVED,
        resolvedAt: new Date(),
        resolvedBy: 'admin_123',
        resolution: 'Fixed in v1.1.0',
      });

      await service.updateStatus(
        'fb_123',
        FeedbackStatus.RESOLVED,
        'admin_123',
        'Fixed in v1.1.0',
      );

      expect(feedbackRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          status: FeedbackStatus.RESOLVED,
          resolvedAt: expect.any(Date),
          resolvedBy: 'admin_123',
          resolution: 'Fixed in v1.1.0',
        }),
      );
    });

    it('should throw error if feedback not found', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(null);

      await expect(
        service.updateStatus('fb_123', FeedbackStatus.RESOLVED, 'admin_123'),
      ).rejects.toThrow(NotFoundException);
    });
  });

  describe('assignFeedback', () => {
    it('should assign feedback to team member', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(mockFeedback);
      mockFeedbackRepository.save.mockResolvedValue({
        ...mockFeedback,
        assignedTo: 'dev_123',
        status: FeedbackStatus.TRIAGED,
      });

      const result = await service.assignFeedback('fb_123', 'dev_123');

      expect(feedbackRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          assignedTo: 'dev_123',
          status: FeedbackStatus.TRIAGED,
        }),
      );
      expect(result.assignedTo).toBe('dev_123');
    });
  });

  describe('addInternalNotes', () => {
    it('should add internal notes', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(mockFeedback);
      mockFeedbackRepository.save.mockResolvedValue({
        ...mockFeedback,
        internalNotes: 'Investigating this issue',
      });

      const result = await service.addInternalNotes(
        'fb_123',
        'Investigating this issue',
      );

      expect(feedbackRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          internalNotes: 'Investigating this issue',
        }),
      );
      expect(result.internalNotes).toBe('Investigating this issue');
    });
  });

  describe('upvoteFeedback', () => {
    it('should upvote feature request', async () => {
      const featureRequest = {
        ...mockFeedback,
        type: FeedbackType.FEATURE_REQUEST,
        upvotes: 5,
      };
      mockFeedbackRepository.findOne.mockResolvedValue(featureRequest);
      mockFeedbackRepository.save.mockResolvedValue({
        ...featureRequest,
        upvotes: 6,
      });

      const result = await service.upvoteFeedback('fb_123', 'user_456');

      expect(feedbackRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          upvotes: 6,
        }),
      );
      expect(analyticsService.trackEvent).toHaveBeenCalledWith(
        expect.objectContaining({
          event: AnalyticsEvent.FEATURE_UPVOTED,
          userId: 'user_456',
        }),
      );
      expect(result.upvotes).toBe(6);
    });

    it('should throw error if not a feature request', async () => {
      mockFeedbackRepository.findOne.mockResolvedValue(mockFeedback);

      await expect(
        service.upvoteFeedback('fb_123', 'user_456'),
      ).rejects.toThrow(BadRequestException);
    });
  });

  describe('getFeedbackStats', () => {
    it('should calculate feedback statistics', async () => {
      const feedbacks = [
        { ...mockFeedback, type: FeedbackType.BUG_REPORT, status: FeedbackStatus.NEW, priority: FeedbackPriority.HIGH },
        { ...mockFeedback, type: FeedbackType.FEATURE_REQUEST, status: FeedbackStatus.TRIAGED, priority: FeedbackPriority.LOW },
        { ...mockFeedback, type: FeedbackType.BUG_REPORT, status: FeedbackStatus.RESOLVED, priority: FeedbackPriority.MEDIUM, resolvedAt: new Date() },
      ];
      mockFeedbackRepository.find.mockResolvedValue(feedbacks);

      const result = await service.getFeedbackStats();

      expect(result.total).toBe(3);
      expect(result.byType[FeedbackType.BUG_REPORT]).toBe(2);
      expect(result.byType[FeedbackType.FEATURE_REQUEST]).toBe(1);
      expect(result.byStatus[FeedbackStatus.NEW]).toBe(1);
      expect(result.byStatus[FeedbackStatus.RESOLVED]).toBe(1);
      expect(result.byPriority[FeedbackPriority.HIGH]).toBe(1);
      expect(result.responseRate).toBeGreaterThan(0);
    });

    it('should handle empty feedback list', async () => {
      mockFeedbackRepository.find.mockResolvedValue([]);

      const result = await service.getFeedbackStats();

      expect(result.total).toBe(0);
      expect(result.averageResolutionTime).toBe(0);
      expect(result.responseRate).toBe(0);
    });
  });

  describe('getTrendingFeatureRequests', () => {
    it('should get feature requests by upvotes', async () => {
      const trending = [
        { ...mockFeedback, type: FeedbackType.FEATURE_REQUEST, upvotes: 50 },
        { ...mockFeedback, type: FeedbackType.FEATURE_REQUEST, upvotes: 30 },
      ];
      mockFeedbackRepository.find.mockResolvedValue(trending);

      const result = await service.getTrendingFeatureRequests(10);

      expect(feedbackRepository.find).toHaveBeenCalledWith({
        where: {
          type: FeedbackType.FEATURE_REQUEST,
          status: expect.any(Object),
        },
        order: { upvotes: 'DESC', createdAt: 'DESC' },
        take: 10,
      });
      expect(result).toEqual(trending);
    });
  });

  describe('detectSentiment', () => {
    it('should detect negative sentiment', () => {
      const sentiment = (service as any).detectSentiment(
        'This is a terrible bug that crashes the app',
      );

      expect(sentiment).toMatch(/negative/);
    });

    it('should detect very negative sentiment', () => {
      const sentiment = (service as any).detectSentiment(
        'Awful terrible broken crash bug error',
      );

      expect(sentiment).toBe('very_negative');
    });

    it('should detect positive sentiment', () => {
      const sentiment = (service as any).detectSentiment(
        'I love this feature, it is great!',
      );

      expect(sentiment).toMatch(/positive/);
    });

    it('should detect very positive sentiment', () => {
      const sentiment = (service as any).detectSentiment(
        'Amazing excellent wonderful fantastic love',
      );

      expect(sentiment).toBe('very_positive');
    });

    it('should detect neutral sentiment', () => {
      const sentiment = (service as any).detectSentiment(
        'The app works as expected',
      );

      expect(sentiment).toBe('neutral');
    });
  });

  describe('calculatePriority', () => {
    it('should assign HIGH priority to negative bug reports', () => {
      const priority = (service as any).calculatePriority(
        FeedbackType.BUG_REPORT,
        'negative',
      );

      expect(priority).toBe(FeedbackPriority.HIGH);
    });

    it('should assign HIGH priority to performance issues', () => {
      const priority = (service as any).calculatePriority(
        FeedbackType.PERFORMANCE_ISSUE,
        'neutral',
      );

      expect(priority).toBe(FeedbackPriority.HIGH);
    });

    it('should assign MEDIUM priority to positive feature requests', () => {
      const priority = (service as any).calculatePriority(
        FeedbackType.FEATURE_REQUEST,
        'very_positive',
      );

      expect(priority).toBe(FeedbackPriority.MEDIUM);
    });

    it('should assign LOW priority to neutral feature requests', () => {
      const priority = (service as any).calculatePriority(
        FeedbackType.FEATURE_REQUEST,
        'neutral',
      );

      expect(priority).toBe(FeedbackPriority.LOW);
    });
  });
});
