/**
 * Temporary dual-write: when ACTIVE_DATABASE=mongodb and a module writes to MySQL,
 * also mirror the write into Mongo so remaining Mongo routes stay consistent.
 *
 * Disable with DUAL_WRITE=false after full cutover.
 */
import {
  isAuthMysql,
  isCitiesMysql,
  isContactMysql,
  isFavoritesMysql,
  isLoyaltyMysql,
  isMongoActive,
  isNotificationsMysql,
  isPropertiesMysql,
  isReviewsMysql,
  isSettingsMysql,
} from "@/db/activeDatabase";
import {
  AppSetting,
  City,
  ContactMessage,
  Favorite,
  LoyaltyAccount,
  LoyaltyTxn,
  Notification,
  Property,
  PropertyUpdate,
  Review,
  User,
  RefreshToken,
  PasswordResetToken,
  EmailVerificationToken,
  OtpChallenge,
} from "@/db/models";
import type { UserRow } from "@/db/mysql/users";
import type { PropertyRow } from "@/db/mysql/properties";
import type { ReviewRow } from "@/db/mysql/reviews";
import type { NotificationRow } from "@/db/mysql/notifications";
import type { FavoriteRow } from "@/db/mysql/favorites";
import type { LoyaltyAccountRow, LoyaltyTxnRow } from "@/db/mysql/loyalty";
import type { ContactMessageRow } from "@/db/mysql/contact";
import type { CityRow } from "@/db/mysql/cities";

export function isDualWriteEnabled() {
  if ((process.env.DUAL_WRITE || "true").toLowerCase().trim() === "false") return false;
  // Only needed while app core is still Mongo and modules write MySQL
  return isMongoActive();
}

function logDualFail(site: string, err: unknown) {
  const msg = err instanceof Error ? err.message : String(err);
  console.error(`[dual-write] FAIL ${site}: ${msg}`);
}

/** Fail the request if mirror fails — keeps DBs from diverging silently on critical auth/user writes. */
async function mirrorStrict(site: string, fn: () => Promise<void>) {
  if (!isDualWriteEnabled()) return;
  try {
    await fn();
  } catch (e) {
    logDualFail(site, e);
    throw e;
  }
}

/** Best-effort mirror for non-auth domain data (log + continue). Prefer strict for money later. */
async function mirrorSoft(site: string, fn: () => Promise<void>) {
  if (!isDualWriteEnabled()) return;
  try {
    await fn();
  } catch (e) {
    logDualFail(site, e);
  }
}

export async function dualWriteUserUpsert(user: UserRow) {
  if (!isAuthMysql()) return;
  await mirrorStrict("user.upsert", async () => {
    await User.findOneAndUpdate(
      { _id: user.id },
      {
        $set: {
          email: user.email,
          passwordHash: user.passwordHash,
          googleSub: user.googleSub,
          fullName: user.fullName,
          phone: user.phone,
          avatarUrl: user.avatarUrl,
          role: user.role,
          status: user.status,
          locale: user.locale,
          emailVerifiedAt: user.emailVerifiedAt ?? undefined,
          phoneVerifiedAt: user.phoneVerifiedAt ?? undefined,
          passportUrl: user.passportUrl ?? undefined,
          ownerVerificationStatus: user.ownerVerificationStatus,
          trustedOwner: user.trustedOwner,
          ownerVerifiedAt: user.ownerVerifiedAt ?? undefined,
          deletedAt: user.deletedAt ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteRefreshToken(input: {
  id: string;
  userId: string;
  tokenHash: string;
  expiresAt: Date;
}) {
  if (!isAuthMysql()) return;
  await mirrorStrict("refreshToken.create", async () => {
    await RefreshToken.findOneAndUpdate(
      { _id: input.id },
      {
        $set: {
          userId: input.userId,
          tokenHash: input.tokenHash,
          expiresAt: input.expiresAt,
          revokedAt: undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteRevokeRefreshByHash(tokenHash: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("refreshToken.revokeByHash", async () => {
    await RefreshToken.updateMany(
      { tokenHash, revokedAt: null },
      { $set: { revokedAt: new Date() } },
    );
  });
}

export async function dualWriteRevokeRefreshById(id: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("refreshToken.revokeById", async () => {
    await RefreshToken.updateOne({ _id: id }, { $set: { revokedAt: new Date() } });
  });
}

export async function dualWriteRevokeAllRefreshForUser(userId: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("refreshToken.revokeAllUser", async () => {
    await RefreshToken.updateMany(
      { userId, revokedAt: null },
      { $set: { revokedAt: new Date() } },
    );
  });
}

export async function dualWritePasswordResetToken(input: {
  id: string;
  userId: string;
  tokenHash: string;
  expiresAt: Date;
}) {
  if (!isAuthMysql()) return;
  await mirrorStrict("passwordReset.create", async () => {
    await PasswordResetToken.findOneAndUpdate(
      { _id: input.id },
      {
        $set: {
          userId: input.userId,
          tokenHash: input.tokenHash,
          expiresAt: input.expiresAt,
          usedAt: undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteMarkPasswordResetUsed(id: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("passwordReset.markUsed", async () => {
    await PasswordResetToken.updateOne({ _id: id }, { $set: { usedAt: new Date() } });
  });
}

export async function dualWriteEmailVerificationToken(input: {
  id: string;
  userId: string;
  tokenHash: string;
  expiresAt: Date;
}) {
  if (!isAuthMysql()) return;
  await mirrorStrict("emailVerification.create", async () => {
    await EmailVerificationToken.findOneAndUpdate(
      { _id: input.id },
      {
        $set: {
          userId: input.userId,
          tokenHash: input.tokenHash,
          expiresAt: input.expiresAt,
          usedAt: undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteMarkEmailVerificationUsed(id: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("emailVerification.markUsed", async () => {
    await EmailVerificationToken.updateOne({ _id: id }, { $set: { usedAt: new Date() } });
  });
}

export async function dualWriteOtpCreate(input: {
  id: string;
  userId: string;
  codeHash: string;
  expiresAt: Date;
}) {
  if (!isAuthMysql()) return;
  await mirrorSoft("otp.create", async () => {
    await OtpChallenge.updateMany(
      { userId: input.userId, channel: "EMAIL", consumedAt: null },
      { $set: { consumedAt: new Date() } },
    );
    await OtpChallenge.findOneAndUpdate(
      { _id: input.id },
      {
        $set: {
          userId: input.userId,
          channel: "EMAIL",
          codeHash: input.codeHash,
          expiresAt: input.expiresAt,
          attempts: 0,
          consumedAt: undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteOtpConsume(id: string) {
  if (!isAuthMysql()) return;
  await mirrorSoft("otp.consume", async () => {
    await OtpChallenge.updateOne({ _id: id }, { $set: { consumedAt: new Date() } });
  });
}

export async function dualWritePropertyUpsert(p: PropertyRow) {
  if (!isPropertiesMysql()) return;
  await mirrorSoft("property.upsert", async () => {
    await Property.findOneAndUpdate(
      { _id: p.id },
      {
        $set: {
          ownerId: p.ownerId,
          cityId: p.cityId,
          titleAr: p.titleAr,
          titleEn: p.titleEn,
          slug: p.slug,
          descriptionAr: p.descriptionAr,
          descriptionEn: p.descriptionEn,
          address: p.address,
          latitude: p.latitude ?? undefined,
          longitude: p.longitude ?? undefined,
          status: p.status,
          bedrooms: p.bedrooms,
          bathrooms: p.bathrooms,
          maxGuests: p.maxGuests,
          wifi: p.wifi,
          parking: p.parking,
          airConditioning: p.airConditioning,
          kitchen: p.kitchen,
          hospitalNearby: p.hospitalNearby,
          universityNearby: p.universityNearby,
          petFriendly: p.petFriendly,
          instantBooking: p.instantBooking,
          basePriceTnd: p.basePriceTnd,
          cleaningFeeTnd: p.cleaningFeeTnd,
          checkInTime: p.checkInTime,
          checkOutTime: p.checkOutTime,
          cancellationPolicy: p.cancellationPolicy,
          houseRules: p.houseRules ?? undefined,
          featured: p.featured,
          deletedAt: p.deletedAt ?? undefined,
          images: p.images.map((m) => ({
            _id: m.id,
            url: m.url,
            sortOrder: m.sortOrder,
            createdAt: m.createdAt ?? new Date(),
          })),
          videos: p.videos.map((m) => ({
            _id: m.id,
            url: m.url,
            sortOrder: m.sortOrder,
            createdAt: m.createdAt ?? new Date(),
          })),
          blockedDates: p.blockedDates,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWritePropertyUpdateDoc(input: {
  id: string;
  propertyId: string;
  ownerId: string;
  titleAr: string;
  titleEn: string;
  bodyAr: string;
  bodyEn: string;
  deletedAt?: Date | null;
}) {
  if (!isPropertiesMysql()) return;
  await mirrorSoft("propertyUpdate.upsert", async () => {
    await PropertyUpdate.findOneAndUpdate(
      { _id: input.id },
      {
        $set: {
          propertyId: input.propertyId,
          ownerId: input.ownerId,
          titleAr: input.titleAr,
          titleEn: input.titleEn,
          bodyAr: input.bodyAr,
          bodyEn: input.bodyEn,
          deletedAt: input.deletedAt ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteFavoriteCreate(f: FavoriteRow) {
  if (!isFavoritesMysql()) return;
  await mirrorSoft("favorite.create", async () => {
    await Favorite.findOneAndUpdate(
      { _id: f.id },
      { $set: { userId: f.userId, propertyId: f.propertyId } },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteFavoriteDelete(id: string) {
  if (!isFavoritesMysql()) return;
  await mirrorSoft("favorite.delete", async () => {
    await Favorite.deleteOne({ _id: id });
  });
}

export async function dualWriteNotificationCreate(n: NotificationRow) {
  if (!isNotificationsMysql()) return;
  await mirrorSoft("notification.create", async () => {
    await Notification.findOneAndUpdate(
      { _id: n.id },
      {
        $set: {
          userId: n.userId,
          titleAr: n.titleAr,
          titleEn: n.titleEn,
          messageAr: n.messageAr,
          messageEn: n.messageEn,
          link: n.link ?? undefined,
          readAt: n.readAt ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteNotificationRead(id: string, readAt: Date) {
  if (!isNotificationsMysql()) return;
  await mirrorSoft("notification.read", async () => {
    await Notification.updateOne({ _id: id }, { $set: { readAt } });
  });
}

export async function dualWriteNotificationReadAll(userId: string, readAt: Date) {
  if (!isNotificationsMysql()) return;
  await mirrorSoft("notification.readAll", async () => {
    await Notification.updateMany({ userId, readAt: null }, { $set: { readAt } });
  });
}

export async function dualWriteReviewUpsert(r: ReviewRow) {
  if (!isReviewsMysql()) return;
  await mirrorSoft("review.upsert", async () => {
    await Review.findOneAndUpdate(
      { _id: r.id },
      {
        $set: {
          bookingId: r.bookingId,
          propertyId: r.propertyId,
          authorId: r.authorId,
          rating: r.rating,
          comment: r.comment ?? undefined,
          ownerReply: r.ownerReply ?? undefined,
          ownerReplyBy: r.ownerReplyBy ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteContactCreate(c: ContactMessageRow) {
  if (!isContactMysql()) return;
  await mirrorSoft("contact.create", async () => {
    await ContactMessage.findOneAndUpdate(
      { _id: c.id },
      {
        $set: {
          userId: c.userId ?? undefined,
          name: c.name,
          email: c.email,
          subject: c.subject,
          message: c.message,
          readAt: c.readAt ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteLoyaltyAccount(a: LoyaltyAccountRow) {
  if (!isLoyaltyMysql()) return;
  await mirrorSoft("loyalty.account", async () => {
    await LoyaltyAccount.findOneAndUpdate(
      { _id: a.id },
      { $set: { userId: a.userId, points: a.points } },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteLoyaltyTxn(t: LoyaltyTxnRow) {
  if (!isLoyaltyMysql()) return;
  await mirrorSoft("loyalty.txn", async () => {
    await LoyaltyTxn.findOneAndUpdate(
      { _id: t.id },
      {
        $set: {
          userId: t.userId,
          type: t.type,
          delta: t.delta,
          pointsAfter: t.pointsAfter,
          bookingId: t.bookingId ?? undefined,
          note: t.note ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteAppSetting(key: string, value: unknown) {
  if (!isSettingsMysql()) return;
  await mirrorSoft("settings.upsert", async () => {
    await AppSetting.findOneAndUpdate(
      { key },
      { $set: { value } },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteCityUpsert(c: CityRow) {
  if (!isCitiesMysql()) return;
  await mirrorSoft("city.upsert", async () => {
    await City.findOneAndUpdate(
      { _id: c.id },
      {
        $set: {
          nameAr: c.nameAr,
          nameEn: c.nameEn,
          slug: c.slug,
          country: c.country,
          region: c.region,
          isTourist: c.isTourist,
          aliases: c.aliases || [],
          blurbEn: c.blurbEn,
          blurbAr: c.blurbAr,
          imageUrl: c.imageUrl ?? undefined,
          deletedAt: c.deletedAt ?? undefined,
        },
      },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
  });
}

export async function dualWriteCitySoftDelete(id: string, deletedAt: Date) {
  if (!isCitiesMysql()) return;
  await mirrorSoft("city.softDelete", async () => {
    await City.findOneAndUpdate(
      { _id: id },
      { $set: { deletedAt } },
      { upsert: false },
    );
  });
}
