import { CreateReviewCommand } from '../../application/commands/create-review/create-review.command';
import { DeleteReviewCommand } from '../../application/commands/delete-review/delete-review.command';
import { GetAverageRatingQuery } from '../../application/queries/get-average-rating/get-average-rating.query';
import { GetReviewsByTargetQuery } from '../../application/queries/get-reviews-by-target/get-reviews-by-target.query';
import { GetReviewsByUserQuery } from '../../application/queries/get-reviews-by-user/get-reviews-by-user.query';
import { ReviewsController } from '../controllers/reviews.controller';

describe('ReviewsController', () => {
  let controller: ReviewsController;
  let mockCommandBus: { execute: ReturnType<typeof vi.fn> };
  let mockQueryBus: { execute: ReturnType<typeof vi.fn> };

  const mockUser = { sub: 'user-1', phone: '0901234567', role: 'BUYER' };

  beforeEach(() => {
    mockCommandBus = { execute: vi.fn() };
    mockQueryBus = { execute: vi.fn() };
    controller = new ReviewsController(mockCommandBus as any, mockQueryBus as any);
  });

  describe('POST /reviews — createReview', () => {
    it('dispatches CreateReviewCommand with correct parameters', async () => {
      const dto = { targetType: 'agent', targetId: 'agent-1', rating: 5, comment: 'Tuyệt vời!' };
      const expected = { id: 'rev-1', rating: 5, targetType: 'agent', targetId: 'agent-1', createdAt: '2026-01-01T00:00:00.000Z' };
      mockCommandBus.execute.mockResolvedValue(expected);

      const result = await controller.createReview(dto as any, mockUser as any);

      expect(mockCommandBus.execute).toHaveBeenCalledWith(
        expect.any(CreateReviewCommand),
      );
      const cmd = mockCommandBus.execute.mock.calls[0]![0] as CreateReviewCommand;
      expect(cmd.userId).toBe('user-1');
      expect(cmd.targetType).toBe('agent');
      expect(cmd.targetId).toBe('agent-1');
      expect(cmd.rating).toBe(5);
      expect(cmd.comment).toBe('Tuyệt vời!');
      expect(result).toEqual(expected);
    });

    it('passes null comment when not provided', async () => {
      const dto = { targetType: 'property', targetId: 'prop-1', rating: 3 };
      mockCommandBus.execute.mockResolvedValue({ id: 'rev-2' });

      await controller.createReview(dto as any, mockUser as any);

      const cmd = mockCommandBus.execute.mock.calls[0]![0] as CreateReviewCommand;
      expect(cmd.comment).toBeNull();
    });
  });

  describe('GET /reviews — getReviewsByTarget', () => {
    it('dispatches GetReviewsByTargetQuery with defaults', async () => {
      const dto = { targetType: 'agent', targetId: 'agent-1' };
      const expected = { data: [], total: 0, page: 1, limit: 20, totalPages: 0 };
      mockQueryBus.execute.mockResolvedValue(expected);

      const result = await controller.getReviewsByTarget(dto as any);

      expect(mockQueryBus.execute).toHaveBeenCalledWith(
        expect.any(GetReviewsByTargetQuery),
      );
      const query = mockQueryBus.execute.mock.calls[0]![0] as GetReviewsByTargetQuery;
      expect(query.targetType).toBe('agent');
      expect(query.targetId).toBe('agent-1');
      expect(query.page).toBe(1);
      expect(query.limit).toBe(20);
      expect(result).toEqual(expected);
    });

    it('passes custom page and limit', async () => {
      const dto = { targetType: 'agent', targetId: 'agent-1', page: 3, limit: 10 };
      mockQueryBus.execute.mockResolvedValue({ data: [] });

      await controller.getReviewsByTarget(dto as any);

      const query = mockQueryBus.execute.mock.calls[0]![0] as GetReviewsByTargetQuery;
      expect(query.page).toBe(3);
      expect(query.limit).toBe(10);
    });
  });

  describe('GET /reviews/stats — getStats', () => {
    it('dispatches GetAverageRatingQuery', async () => {
      const dto = { targetType: 'agent', targetId: 'agent-1' };
      const expected = { targetType: 'agent', targetId: 'agent-1', averageRating: 4.5, totalReviews: 10, distribution: {} };
      mockQueryBus.execute.mockResolvedValue(expected);

      const result = await controller.getStats(dto as any);

      expect(mockQueryBus.execute).toHaveBeenCalledWith(
        expect.any(GetAverageRatingQuery),
      );
      const query = mockQueryBus.execute.mock.calls[0]![0] as GetAverageRatingQuery;
      expect(query.targetType).toBe('agent');
      expect(query.targetId).toBe('agent-1');
      expect(result).toEqual(expected);
    });
  });

  describe('GET /reviews/me — getMyReviews', () => {
    it('dispatches GetReviewsByUserQuery with defaults', async () => {
      const expected = { data: [], total: 0, page: 1, limit: 20, totalPages: 0 };
      mockQueryBus.execute.mockResolvedValue(expected);

      const result = await controller.getMyReviews(mockUser as any);

      expect(mockQueryBus.execute).toHaveBeenCalledWith(
        expect.any(GetReviewsByUserQuery),
      );
      const query = mockQueryBus.execute.mock.calls[0]![0] as GetReviewsByUserQuery;
      expect(query.userId).toBe('user-1');
      expect(query.page).toBe(1);
      expect(query.limit).toBe(20);
      expect(result).toEqual(expected);
    });
  });

  describe('DELETE /reviews/:id — deleteReview', () => {
    it('dispatches DeleteReviewCommand and returns { deleted: true }', async () => {
      mockCommandBus.execute.mockResolvedValue(undefined);

      const result = await controller.deleteReview('rev-1', mockUser as any);

      expect(mockCommandBus.execute).toHaveBeenCalledWith(
        expect.any(DeleteReviewCommand),
      );
      const cmd = mockCommandBus.execute.mock.calls[0]![0] as DeleteReviewCommand;
      expect(cmd.reviewId).toBe('rev-1');
      expect(cmd.userId).toBe('user-1');
      expect(result).toEqual({ deleted: true });
    });
  });
});
