import { Router } from "express";
import { z } from "zod";
import { Booking, Review, Property, User } from "@/db/models";
import { AppError, asyncHandler } from "@/lib/errors";
import { requireAuth, requireRoles } from "@/middleware/auth";
import { notifyUser } from "@/services/notifications";
import { toPlain } from "@/lib/serialize";
import { isReviewsMysql, isPropertiesMysql, isBookingsMysql, isAuthMysql } from "@/db/activeDatabase";
import {
  createReviewMysql,
  findReviewByBookingMysql,
  findReviewByIdMysql,
  reviewToApi,
  setOwnerReplyMysql,
} from "@/db/mysql/reviews";
import { findPropertyByIdMysql } from "@/db/mysql/properties";
import { findBookingByIdMysql } from "@/db/mysql/bookings";
import { findUsersByIdsMysql } from "@/db/mysql/users";
import { isFinancialDualWriteEnabled } from "@/db/dualWriteFinancial";
import { upsertBookingMysql } from "@/db/mysql/financialWrites";
import { syncBookingToMysql } from "@/services/ledger";
import { isStaffRole } from "@/lib/auth/rbac";

export const reviewsRouter = Router();

reviewsRouter.post(
  "/",
  requireAuth,
  requireRoles("CUSTOMER", "ADMIN"),
  asyncHandler(async (req, res) => {
    const body = z
      .object({
        bookingId: z.string().min(1),
        rating: z.coerce.number().int().min(1).max(5),
        comment: z.string().max(2000).optional(),
      })
      .parse(req.body);

    // Booking eligibility follows BOOKINGS_DATABASE
    let booking: any = null;
    if (isBookingsMysql()) {
      const row = await findBookingByIdMysql(body.bookingId);
      if (row && row.customerId === req.user!.id) booking = row;
    } else {
      booking = await Booking.findOne({
        _id: body.bookingId,
        customerId: req.user!.id,
        deletedAt: null,
      }).lean();
    }
    if (!booking) throw new AppError(404, "Booking not found");
    if (!["CONFIRMED", "COMPLETED"].includes(booking.status)) {
      throw new AppError(409, "Booking not eligible for review");
    }

    const bookingId = String(booking.id || booking._id);
    const propertyId = String(booking.propertyId);
    const ownerId = String(booking.ownerId);

    if (isReviewsMysql()) {
      const existing = await findReviewByBookingMysql(bookingId);
      if (existing) throw new AppError(409, "Already reviewed");

      const review = await createReviewMysql({
        bookingId,
        propertyId,
        authorId: req.user!.id,
        rating: body.rating,
        comment: body.comment,
      });

      if (booking.status === "CONFIRMED") {
        if (isBookingsMysql() && !isFinancialDualWriteEnabled()) {
          const row = await findBookingByIdMysql(bookingId);
          if (row) {
            await upsertBookingMysql({
              ...row,
              status: "COMPLETED",
              updatedAt: new Date(),
            });
          }
        } else {
          await Booking.updateOne({ _id: bookingId }, { $set: { status: "COMPLETED" } });
          if (isFinancialDualWriteEnabled()) {
            const b = await Booking.findById(bookingId);
            if (b) {
              b.status = "COMPLETED";
              await syncBookingToMysql(b as any);
            }
          }
        }
      }

      await notifyUser({
        userId: ownerId,
        titleAr: "تقييم جديد",
        titleEn: "New review",
        messageAr: `حصل عقارك على تقييم ${body.rating}/5`,
        messageEn: `Your property received a ${body.rating}/5 review`,
        link: `/apartments/${propertyId}`,
      });
      let author: any = null;
      if (isAuthMysql()) {
        author = (await findUsersByIdsMysql([req.user!.id]))[0] || null;
      } else {
        author = await User.findById(req.user!.id).select("fullName avatarUrl").lean();
      }
      res.status(201).json({
        review: {
          ...reviewToApi(review),
          author: author
            ? {
                id: author.id ?? author._id,
                fullName: author.fullName,
                avatarUrl: author.avatarUrl,
              }
            : undefined,
        },
        _db: "mysql",
      });
      return;
    }

    const existing = await Review.findOne({ bookingId: booking._id }).lean();
    if (existing) throw new AppError(409, "Already reviewed");

    const review = await Review.create({
      bookingId: booking._id,
      propertyId: booking.propertyId,
      authorId: req.user!.id,
      rating: body.rating,
      comment: body.comment ?? undefined,
    });

    if (booking.status === "CONFIRMED") {
      await Booking.updateOne({ _id: booking._id }, { $set: { status: "COMPLETED" } });
    }

    await notifyUser({
      userId: booking.ownerId,
      titleAr: "تقييم جديد",
      titleEn: "New review",
      messageAr: `حصل عقارك على تقييم ${body.rating}/5`,
      messageEn: `Your property received a ${body.rating}/5 review`,
      link: `/apartments/${booking.propertyId}`,
    });

    const author = await User.findById(req.user!.id).select("fullName avatarUrl").lean();
    res.status(201).json({
      review: {
        ...toPlain(review.toObject()),
        author: author
          ? { id: author._id, fullName: author.fullName, avatarUrl: author.avatarUrl }
          : undefined,
      },
      _db: "mongodb",
    });
  }),
);

reviewsRouter.post(
  "/:id/reply",
  requireAuth,
  requireRoles("OWNER", "ADMIN"),
  asyncHandler(async (req, res) => {
    const reply = z.string().min(1).max(2000).parse(req.body.ownerReply);

    if (isReviewsMysql()) {
      const review = await findReviewByIdMysql(req.params.id);
      if (!review) throw new AppError(404, "Review not found");

      let ownerId: string | undefined;
      if (isPropertiesMysql()) {
        const property = await findPropertyByIdMysql(review.propertyId);
        if (!property) throw new AppError(404, "Property not found");
        ownerId = property.ownerId;
      } else {
        const property = await Property.findById(review.propertyId).lean();
        if (!property) throw new AppError(404, "Property not found");
        ownerId = property.ownerId;
      }

      if (!isStaffRole(req.user!.role) && ownerId !== req.user!.id) {
        throw new AppError(403, "Forbidden");
      }

      const updated = await setOwnerReplyMysql(review.id, reply, req.user!.id);
      res.json({ review: reviewToApi(updated!), _db: "mysql" });
      return;
    }

    const review = await Review.findById(req.params.id);
    if (!review) throw new AppError(404, "Review not found");

    const property = await Property.findById(review.propertyId).lean();
    if (!property) throw new AppError(404, "Property not found");
    if (!isStaffRole(req.user!.role) && property.ownerId !== req.user!.id) {
      throw new AppError(403, "Forbidden");
    }

    review.ownerReply = reply;
    review.ownerReplyBy = req.user!.id;
    await review.save();
    res.json({ review: toPlain(review.toObject()), _db: "mongodb" });
  }),
);
