import type pg from 'pg';
import type { ReviewsRepository } from '../domain/ports.js';
import type {
  ProductReviewAggregate,
  PublishedReview,
  Review,
  ReviewStatus,
  SubmitReviewCommand,
} from '../domain/review.js';

interface ReviewRow {
  id: string;
  user_id: string;
  product_id: string;
  order_id: string;
  order_item_id: string;
  rating: number;
  title: string;
  body: string;
  status: ReviewStatus;
  created_at: Date;
  updated_at: Date;
}

export class PgReviewsRepository implements ReviewsRepository {
  constructor(private readonly pool: pg.Pool) {}

  async insert(command: SubmitReviewCommand): Promise<Review | undefined> {
    const result = await this.pool.query<ReviewRow>(
      `INSERT INTO reviews_reviews (user_id, product_id, order_id, order_item_id, rating, title, body)
       VALUES ($1, $2, (SELECT order_id FROM orders_items WHERE id = $3), $3, $4, $5, $6)
       ON CONFLICT (order_item_id) DO NOTHING
       RETURNING *`,
      [
        command.userId,
        command.productId,
        command.orderItemId,
        command.rating,
        command.title,
        command.body,
      ],
    );
    const row = result.rows[0];
    return row ? toReview(row) : undefined;
  }

  async setStatus(id: string, status: 'published' | 'rejected'): Promise<Review | undefined> {
    const result = await this.pool.query<ReviewRow>(
      `UPDATE reviews_reviews SET status = $2, updated_at = now() WHERE id = $1 RETURNING *`,
      [id, status],
    );
    const row = result.rows[0];
    return row ? toReview(row) : undefined;
  }

  async listPublishedByProduct(productId: string): Promise<PublishedReview[]> {
    const result = await this.pool.query<ReviewRow>(
      `SELECT * FROM reviews_reviews WHERE product_id = $1 AND status = 'published' ORDER BY created_at DESC`,
      [productId],
    );
    return result.rows.map(toPublishedReview);
  }

  async aggregate(productId: string): Promise<ProductReviewAggregate> {
    const result = await this.pool.query<{ product_id: string; average: string; count: string }>(
      `SELECT $1::uuid AS product_id,
              COALESCE(AVG(rating), 0)::float AS average,
              COUNT(*)::int AS count
         FROM reviews_reviews WHERE product_id = $1 AND status = 'published'`,
      [productId],
    );
    const row = result.rows[0];
    return {
      productId: row?.product_id ?? productId,
      averageRating: Number(row?.average ?? 0),
      count: Number(row?.count ?? 0),
    };
  }

  async findByOrderItemId(orderItemId: string): Promise<Review | undefined> {
    const result = await this.pool.query<ReviewRow>(
      'SELECT * FROM reviews_reviews WHERE order_item_id = $1',
      [orderItemId],
    );
    const row = result.rows[0];
    return row ? toReview(row) : undefined;
  }

  async listAll(params?: {
    status?: string;
    limit?: number;
    offset?: number;
  }): Promise<{ items: Review[]; total: number }> {
    const conditions: string[] = [];
    const values: unknown[] = [];
    let idx = 1;

    if (params?.status) {
      conditions.push(`status = $${idx++}`);
      values.push(params.status);
    }

    const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
    const limit = params?.limit ?? 20;
    const offset = params?.offset ?? 0;

    const countResult = await this.pool.query<{ count: string }>(
      `SELECT COUNT(*)::text AS count FROM reviews_reviews ${where}`,
      values,
    );
    const total = parseInt(countResult.rows[0]?.count ?? '0', 10);

    const result = await this.pool.query<ReviewRow>(
      `SELECT * FROM reviews_reviews ${where} ORDER BY created_at DESC LIMIT $${idx++} OFFSET $${idx}`,
      [...values, limit, offset],
    );

    return { items: result.rows.map(toReview), total };
  }
}

function toReview(row: ReviewRow): Review {
  return {
    id: row.id,
    userId: row.user_id,
    productId: row.product_id,
    orderId: row.order_id,
    orderItemId: row.order_item_id,
    rating: row.rating,
    title: row.title,
    body: row.body,
    status: row.status,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

function toPublishedReview(row: ReviewRow): PublishedReview {
  return {
    id: row.id,
    productId: row.product_id,
    userId: row.user_id,
    rating: row.rating,
    title: row.title,
    body: row.body,
    createdAt: row.created_at,
  };
}
