import { describe, expect, it } from 'vitest';
import { ReviewsService } from '../application/reviews-service.js';
import {
  DuplicateReviewError,
  InvalidReviewError,
  ReviewNotEligibleError,
  ReviewNotFoundError,
} from '../domain/errors.js';
import type { OrderItemVerifier, ReviewsRepository } from '../domain/ports.js';
import type {
  ProductReviewAggregate,
  PublishedReview,
  Review,
  SubmitReviewCommand,
} from '../domain/review.js';

const REVIEW: Review = {
  id: 'review-1',
  userId: 'user-1',
  productId: 'product-1',
  orderId: 'order-1',
  orderItemId: 'item-1',
  rating: 5,
  title: 'Great',
  body: 'Excellent product',
  status: 'pending',
  createdAt: new Date(),
  updatedAt: new Date(),
};

function repo(overrides: Partial<ReviewsRepository> = {}): ReviewsRepository {
  return {
    insert: async () => REVIEW,
    setStatus: async () => ({ ...REVIEW, status: 'published' }),
    listPublishedByProduct: async () => [],
    listAll: async () => ({ items: [], total: 0 }),
    aggregate: async () =>
      ({ productId: 'product-1', averageRating: 0, count: 0 }) satisfies ProductReviewAggregate,
    findByOrderItemId: async () => undefined,
    ...overrides,
  };
}

function verifier(eligible: boolean): OrderItemVerifier {
  return { isDeliveredForUser: async () => eligible };
}

const SUBMIT: SubmitReviewCommand = {
  userId: 'user-1',
  productId: 'product-1',
  orderItemId: 'item-1',
  rating: 5,
  title: 'Great',
  body: 'Excellent product',
};

describe('ReviewsService', () => {
  it('rejects submission when order item is not delivered (AC1)', async () => {
    const service = new ReviewsService(repo(), verifier(false));
    await expect(service.submit(SUBMIT)).rejects.toBeInstanceOf(ReviewNotEligibleError);
  });

  it('rejects duplicate review (AC2)', async () => {
    const service = new ReviewsService(
      repo({ findByOrderItemId: async () => REVIEW }),
      verifier(true),
    );
    await expect(service.submit(SUBMIT)).rejects.toBeInstanceOf(DuplicateReviewError);
  });

  it('rejects invalid rating', async () => {
    const service = new ReviewsService(repo(), verifier(true));
    await expect(service.submit({ ...SUBMIT, rating: 6 })).rejects.toBeInstanceOf(
      InvalidReviewError,
    );
  });

  it('creates review when eligible', async () => {
    const service = new ReviewsService(repo(), verifier(true));
    const review = await service.submit(SUBMIT);
    expect(review.id).toBe('review-1');
  });

  it('moderates review and throws when missing', async () => {
    const service = new ReviewsService(repo({ setStatus: async () => undefined }), verifier(true));
    await expect(service.moderate('missing', 'published')).rejects.toBeInstanceOf(
      ReviewNotFoundError,
    );
  });

  it('returns published reviews with aggregate', async () => {
    const aggregate: ProductReviewAggregate = {
      productId: 'product-1',
      averageRating: 4.5,
      count: 2,
    };
    const items: PublishedReview[] = [
      {
        id: REVIEW.id,
        productId: REVIEW.productId,
        userId: REVIEW.userId,
        rating: REVIEW.rating,
        title: REVIEW.title,
        body: REVIEW.body,
        createdAt: REVIEW.createdAt,
      },
    ];
    const service = new ReviewsService(
      repo({ listPublishedByProduct: async () => items, aggregate: async () => aggregate }),
      verifier(true),
    );
    const result = await service.listPublishedByProduct('product-1');
    expect(result).toEqual({ items, aggregate });
  });
});
