import { Types } from "mongoose";
import GasStationReviewModel from "../models/GasStationReviewModel";
import GasStationModel from "../models/GasStationModel";
import { gasStationApprovedForAppFilter } from "../utils/gasStationVisibility";
import { buildImageUrl } from "../utils/imageUrl";
import { PaginatedResult } from "./connectionService";

async function assertReviewableStation(gasStationId: string, userId: string) {
  if (!Types.ObjectId.isValid(gasStationId)) {
    throw new Error("STATION_NOT_FOUND");
  }

  const station = await GasStationModel.findOne({
    _id: new Types.ObjectId(gasStationId),
    ...gasStationApprovedForAppFilter(),
  })
    .select("_id userId name")
    .lean();

  if (!station) {
    const any = await GasStationModel.findById(gasStationId).select("_id").lean();
    throw new Error(any ? "STATION_UNAVAILABLE" : "STATION_NOT_FOUND");
  }

  if (String(station.userId) === userId) {
    throw new Error("CANNOT_REVIEW_OWN");
  }

  return station;
}

function mapReview(row: Record<string, unknown>) {
  const user = row.userId as
    | {
        _id: Types.ObjectId;
        fullName?: string;
        email?: string;
        image?: string | null;
      }
    | Types.ObjectId
    | null;

  const populated =
    user && typeof user === "object" && "_id" in user && "fullName" in user
      ? user
      : null;

  return {
    _id: row._id,
    gasStationId: row.gasStationId,
    rating: row.rating,
    comment: row.comment ?? null,
    user: populated
      ? {
          _id: populated._id,
          fullName: populated.fullName ?? "",
          email: populated.email ?? "",
          image: populated.image ? buildImageUrl(populated.image) : null,
        }
      : { _id: user },
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
  };
}

export async function createGasStationReview(
  userId: string,
  gasStationId: string,
  rating: number,
  comment?: string,
): Promise<Record<string, unknown>> {
  await assertReviewableStation(gasStationId, userId);

  const review = await GasStationReviewModel.create({
    gasStationId: new Types.ObjectId(gasStationId),
    userId: new Types.ObjectId(userId),
    rating,
    comment: comment?.trim() || null,
  });

  const populated = await GasStationReviewModel.findById(review._id)
    .populate("userId", "fullName email image")
    .lean();

  return mapReview(populated as Record<string, unknown>);
}

export async function listGasStationReviews(
  gasStationId: string,
  page: number,
  limit: number,
): Promise<
  PaginatedResult<Record<string, unknown>> & {
    averageRating: number;
    totalReviews: number;
  }
> {
  if (!Types.ObjectId.isValid(gasStationId)) {
    throw new Error("STATION_NOT_FOUND");
  }

  const station = await GasStationModel.findOne({
    _id: new Types.ObjectId(gasStationId),
    ...gasStationApprovedForAppFilter(),
  })
    .select("_id")
    .lean();

  if (!station) {
    const any = await GasStationModel.findById(gasStationId).select("_id").lean();
    throw new Error(any ? "STATION_UNAVAILABLE" : "STATION_NOT_FOUND");
  }

  const filter = { gasStationId: new Types.ObjectId(gasStationId) };
  const skip = (page - 1) * limit;

  const [total, rows, agg] = await Promise.all([
    GasStationReviewModel.countDocuments(filter),
    GasStationReviewModel.find(filter)
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .populate("userId", "fullName email image")
      .lean(),
    GasStationReviewModel.aggregate<{
      avg: number;
      count: number;
    }>([
      { $match: filter },
      {
        $group: {
          _id: null,
          avg: { $avg: "$rating" },
          count: { $sum: 1 },
        },
      },
    ]),
  ]);

  const averageRating =
    agg[0]?.count > 0
      ? Math.round((agg[0].avg + Number.EPSILON) * 10) / 10
      : 0;

  return {
    data: rows.map((r) => mapReview(r as Record<string, unknown>)),
    total,
    page,
    totalPages: Math.ceil(total / limit) || 0,
    averageRating,
    totalReviews: agg[0]?.count ?? 0,
  };
}

export async function deleteGasStationReview(
  userId: string,
  gasStationId: string,
  reviewId: string,
): Promise<void> {
  if (
    !Types.ObjectId.isValid(gasStationId) ||
    !Types.ObjectId.isValid(reviewId)
  ) {
    throw new Error("NOT_FOUND");
  }

  const review = await GasStationReviewModel.findOne({
    _id: new Types.ObjectId(reviewId),
    gasStationId: new Types.ObjectId(gasStationId),
  });

  if (!review) throw new Error("NOT_FOUND");
  if (String(review.userId) !== userId) throw new Error("FORBIDDEN");

  await review.deleteOne();
}
