import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { NoticeService } from './notice.service';
import { NoticeGateway } from './notice.gateway';
import { Notice, NoticeStatus, NoticeType } from './notice.entity';

describe('NoticeService', () => {
  let service: NoticeService;
  let repository: Repository<Notice>;
  let gateway: NoticeGateway;

  const mockRepository = {
    create: jest.fn(),
    save: jest.fn(),
    find: jest.fn(),
    findOne: jest.fn(),
    count: jest.fn(),
    createQueryBuilder: jest.fn(),
  };

  const mockGateway = {
    sendToUser: jest.fn(),
    broadcast: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        NoticeService,
        {
          provide: getRepositoryToken(Notice),
          useValue: mockRepository,
        },
        {
          provide: NoticeGateway,
          useValue: mockGateway,
        },
      ],
    }).compile();

    service = module.get<NoticeService>(NoticeService);
    repository = module.get<Repository<Notice>>(getRepositoryToken(Notice));
    gateway = module.get<NoticeGateway>(NoticeGateway);
  });

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

  describe('create', () => {
    it('should create and send notice immediately when no scheduledAt', async () => {
      const createDto = {
        title: 'Test Notice',
        content: 'Test Content',
        userId: 1,
      };

      const mockNotice = {
        id: 1,
        ...createDto,
        status: NoticeStatus.PENDING,
        type: NoticeType.SYSTEM,
        scheduledAt: null,
      };

      mockRepository.create.mockReturnValue(mockNotice);
      mockRepository.save.mockResolvedValueOnce(mockNotice);
      mockRepository.save.mockResolvedValueOnce({
        ...mockNotice,
        status: NoticeStatus.SENT,
        sentAt: new Date(),
      });

      const result = await service.create(createDto);

      expect(mockRepository.create).toHaveBeenCalledWith({
        ...createDto,
        scheduledAt: null,
      });
      expect(mockRepository.save).toHaveBeenCalledTimes(2);
      expect(mockGateway.sendToUser).toHaveBeenCalledWith(1, {
        type: 'notice',
        data: mockNotice,
      });
    });

    it('should create scheduled notice without sending immediately', async () => {
      const scheduledAt = new Date(Date.now() + 3600000); // 1 hour later
      const createDto = {
        title: 'Scheduled Notice',
        content: 'Scheduled Content',
        scheduledAt: scheduledAt.toISOString(),
      };

      const mockNotice = {
        id: 1,
        ...createDto,
        scheduledAt,
        status: NoticeStatus.PENDING,
      };

      mockRepository.create.mockReturnValue(mockNotice);
      mockRepository.save.mockResolvedValue(mockNotice);

      const result = await service.create(createDto);

      expect(mockGateway.sendToUser).not.toHaveBeenCalled();
      expect(mockGateway.broadcast).not.toHaveBeenCalled();
    });
  });

  describe('sendSystemNotice', () => {
    it('should create and send system notice', async () => {
      const mockNotice = {
        id: 1,
        title: 'System Notice',
        content: 'System Content',
        type: NoticeType.SYSTEM,
        userId: 1,
        status: NoticeStatus.PENDING,
      };

      mockRepository.create.mockReturnValue(mockNotice);
      mockRepository.save.mockResolvedValueOnce(mockNotice);
      mockRepository.save.mockResolvedValueOnce({
        ...mockNotice,
        status: NoticeStatus.SENT,
      });

      const result = await service.sendSystemNotice('System Notice', 'System Content', 1);

      expect(result.type).toBe(NoticeType.SYSTEM);
      expect(mockGateway.sendToUser).toHaveBeenCalled();
    });
  });

  describe('sendBroadcast', () => {
    it('should create and send broadcast notice', async () => {
      const mockNotice = {
        id: 1,
        title: 'Broadcast Notice',
        content: 'Broadcast Content',
        type: NoticeType.BROADCAST,
        userId: null,
        status: NoticeStatus.PENDING,
      };

      mockRepository.create.mockReturnValue(mockNotice);
      mockRepository.save.mockResolvedValueOnce(mockNotice);
      mockRepository.save.mockResolvedValueOnce({
        ...mockNotice,
        status: NoticeStatus.SENT,
      });

      const result = await service.sendBroadcast('Broadcast Notice', 'Broadcast Content');

      expect(result.type).toBe(NoticeType.BROADCAST);
      expect(mockGateway.broadcast).toHaveBeenCalled();
    });
  });

  describe('markAsRead', () => {
    it('should mark notice as read', async () => {
      const mockNotice = {
        id: 1,
        userId: 1,
        status: NoticeStatus.SENT,
      };

      const updatedNotice = {
        ...mockNotice,
        status: NoticeStatus.READ,
        readAt: new Date(),
      };

      mockRepository.findOne.mockResolvedValue(mockNotice);
      mockRepository.save.mockResolvedValue(updatedNotice);

      const result = await service.markAsRead(1, 1);

      expect(result.status).toBe(NoticeStatus.READ);
      expect(result.readAt).toBeDefined();
    });
  });

  describe('getUserUnreadCount', () => {
    it('should return unread count for user', async () => {
      mockRepository.count.mockResolvedValue(5);

      const count = await service.getUserUnreadCount(1);

      expect(count).toBe(5);
      expect(mockRepository.count).toHaveBeenCalledWith({
        where: [
          { userId: 1, status: NoticeStatus.SENT },
          { userId: null, status: NoticeStatus.SENT },
        ],
      });
    });
  });
});