import "dotenv/config";
import { Prisma } from "@prisma/client";
import { MongoClient } from "mongodb";
import { prisma } from "../db.js";
import {
  fromRegistrationStatusFa,
  fromResultStateFa,
  type DraftPayloadShape,
  type RegistrationStatusFa,
  type ResultStateFa,
} from "../db/mappers.js";

type MongoDoc = Record<string, unknown> & { _id: { toHexString(): string } };

function getMongoUri(): string {
  return process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/danaschool";
}

function getDatabaseNameFromUri(uri: string): string {
  const parsed = new URL(uri);
  return parsed.pathname.replace(/^\//, "") || "danaschool";
}

function toId(doc: MongoDoc): string {
  return doc._id.toHexString();
}

function asDate(input: unknown): Date {
  if (input instanceof Date) {
    return input;
  }
  return new Date(String(input ?? Date.now()));
}

async function migrateUsers(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("users").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    await prisma.user.upsert({
      where: { id: toId(doc) },
      update: {
        fullName: String(doc.fullName ?? ""),
        nationalId: String(doc.nationalId ?? ""),
        phone: String(doc.phone ?? ""),
        email: String(doc.email ?? ""),
        passwordHash: String(doc.passwordHash ?? ""),
        roles: Array.isArray(doc.roles) ? doc.roles : ["user"],
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        fullName: String(doc.fullName ?? ""),
        nationalId: String(doc.nationalId ?? ""),
        phone: String(doc.phone ?? ""),
        email: String(doc.email ?? ""),
        passwordHash: String(doc.passwordHash ?? ""),
        roles: Array.isArray(doc.roles) ? doc.roles : ["user"],
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migrateExams(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("exams").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    await prisma.exam.upsert({
      where: { id: toId(doc) },
      update: {
        title: String(doc.title ?? ""),
        desc: String(doc.desc ?? ""),
        date: String(doc.date ?? ""),
        time: String(doc.time ?? ""),
        venue: String(doc.venue ?? ""),
        cap: Number(doc.cap ?? 0),
        fee: Number(doc.fee ?? 0),
        deadline: String(doc.deadline ?? ""),
        state: String(doc.state ?? "فعال"),
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        title: String(doc.title ?? ""),
        desc: String(doc.desc ?? ""),
        date: String(doc.date ?? ""),
        time: String(doc.time ?? ""),
        venue: String(doc.venue ?? ""),
        cap: Number(doc.cap ?? 0),
        fee: Number(doc.fee ?? 0),
        deadline: String(doc.deadline ?? ""),
        state: String(doc.state ?? "فعال"),
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migrateRegistrationDrafts(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("registrationdrafts").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    const userId = String((doc.userId as { toHexString?: () => string })?.toHexString?.() ?? "");
    if (!userId) continue;
    await prisma.registrationDraft.upsert({
      where: { userId },
      update: {
        step: Number(doc.step ?? 1),
        payload: (doc.payload ?? {}) as DraftPayloadShape,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        userId,
        step: Number(doc.step ?? 1),
        payload: (doc.payload ?? {}) as DraftPayloadShape,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migrateRegistrations(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("registrations").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    const userId = String((doc.userId as { toHexString?: () => string })?.toHexString?.() ?? "");
    const examId = String((doc.examId as { toHexString?: () => string })?.toHexString?.() ?? "");
    if (!userId || !examId) continue;
    await prisma.registration.upsert({
      where: { id: toId(doc) },
      update: {
        userId,
        examId,
        draftSnapshot: (doc.draftSnapshot ?? {}) as DraftPayloadShape,
        status: fromRegistrationStatusFa(
          (String(doc.status ?? "در انتظار تایید") as RegistrationStatusFa) ?? "در انتظار تایید",
        ),
        trackingCode: doc.trackingCode ? String(doc.trackingCode) : null,
        payableAmount: Number(doc.payableAmount ?? 0),
        seatNumber: doc.seatNumber ? Number(doc.seatNumber) : null,
        candidateCode: String(doc.candidateCode ?? toId(doc)),
        resultState: fromResultStateFa(
          (String(doc.resultState ?? "نامشخص") as ResultStateFa) ?? "نامشخص",
        ),
        resultScore: doc.resultScore ? Number(doc.resultScore) : null,
        resultRank: doc.resultRank ? Number(doc.resultRank) : null,
        paymentUrl: doc.paymentUrl ? String(doc.paymentUrl) : null,
        rejectionReason: doc.rejectionReason ? String(doc.rejectionReason) : null,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        userId,
        examId,
        draftSnapshot: (doc.draftSnapshot ?? {}) as DraftPayloadShape,
        status: fromRegistrationStatusFa(
          (String(doc.status ?? "در انتظار تایید") as RegistrationStatusFa) ?? "در انتظار تایید",
        ),
        trackingCode: doc.trackingCode ? String(doc.trackingCode) : null,
        payableAmount: Number(doc.payableAmount ?? 0),
        seatNumber: doc.seatNumber ? Number(doc.seatNumber) : null,
        candidateCode: String(doc.candidateCode ?? toId(doc)),
        resultState: fromResultStateFa(
          (String(doc.resultState ?? "نامشخص") as ResultStateFa) ?? "نامشخص",
        ),
        resultScore: doc.resultScore ? Number(doc.resultScore) : null,
        resultRank: doc.resultRank ? Number(doc.resultRank) : null,
        paymentUrl: doc.paymentUrl ? String(doc.paymentUrl) : null,
        rejectionReason: doc.rejectionReason ? String(doc.rejectionReason) : null,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migratePaymentSessions(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("paymentsessions").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    const registrationId = String(
      (doc.registrationId as { toHexString?: () => string })?.toHexString?.() ?? "",
    );
    const userId = String((doc.userId as { toHexString?: () => string })?.toHexString?.() ?? "");
    if (!registrationId || !userId) continue;
    const statusRaw = String(doc.status ?? "pending");
    const status = statusRaw === "success" || statusRaw === "failed" ? statusRaw : "pending";
    await prisma.paymentSession.upsert({
      where: { id: toId(doc) },
      update: {
        registrationId,
        userId,
        status,
        transactionId: doc.transactionId ? String(doc.transactionId) : null,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        registrationId,
        userId,
        status,
        transactionId: doc.transactionId ? String(doc.transactionId) : null,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migrateOtpSessions(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("otpsessions").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    await prisma.otpSession.upsert({
      where: { id: toId(doc) },
      update: {
        phone: String(doc.phone ?? ""),
        phoneMasked: String(doc.phoneMasked ?? ""),
        code: String(doc.code ?? ""),
        expiresAt: asDate(doc.expiresAt),
        payload: (doc.payload ?? {}) as Prisma.InputJsonValue,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        phone: String(doc.phone ?? ""),
        phoneMasked: String(doc.phoneMasked ?? ""),
        code: String(doc.code ?? ""),
        expiresAt: asDate(doc.expiresAt),
        payload: (doc.payload ?? {}) as Prisma.InputJsonValue,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migratePasswordResetSessions(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("passwordresetsessions").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    const userId = String((doc.userId as { toHexString?: () => string })?.toHexString?.() ?? "");
    if (!userId) continue;
    await prisma.passwordResetSession.upsert({
      where: { id: toId(doc) },
      update: {
        phone: String(doc.phone ?? ""),
        phoneMasked: String(doc.phoneMasked ?? ""),
        code: String(doc.code ?? ""),
        expiresAt: asDate(doc.expiresAt),
        userId,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        phone: String(doc.phone ?? ""),
        phoneMasked: String(doc.phoneMasked ?? ""),
        code: String(doc.code ?? ""),
        expiresAt: asDate(doc.expiresAt),
        userId,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function migrateAdminSettings(db: ReturnType<MongoClient["db"]>): Promise<void> {
  const docs = (await db.collection("adminsettings").find({}).toArray()) as MongoDoc[];
  for (const doc of docs) {
    const singletonKey = String(doc.singletonKey ?? "default");
    await prisma.adminSettings.upsert({
      where: { singletonKey },
      update: {
        appName: String(doc.appName ?? "دانا و توانا"),
        supportEmail: String(doc.supportEmail ?? "support@example.com"),
        payment: (doc.payment ?? { merchantId: "", apiKeyMasked: "****" }) as Prisma.InputJsonValue,
        notifications: (doc.notifications ?? {
          smsAfterPayment: true,
          email24hReminder: false,
        }) as Prisma.InputJsonValue,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
      create: {
        id: toId(doc),
        singletonKey,
        appName: String(doc.appName ?? "دانا و توانا"),
        supportEmail: String(doc.supportEmail ?? "support@example.com"),
        payment: (doc.payment ?? { merchantId: "", apiKeyMasked: "****" }) as Prisma.InputJsonValue,
        notifications: (doc.notifications ?? {
          smsAfterPayment: true,
          email24hReminder: false,
        }) as Prisma.InputJsonValue,
        createdAt: asDate(doc.createdAt),
        updatedAt: asDate(doc.updatedAt),
      },
    });
  }
}

async function main(): Promise<void> {
  const mongoUri = getMongoUri();
  const client = new MongoClient(mongoUri);
  await client.connect();
  await prisma.$connect();
  try {
    const db = client.db(getDatabaseNameFromUri(mongoUri));
    await migrateUsers(db);
    await migrateExams(db);
    await migrateRegistrationDrafts(db);
    await migrateRegistrations(db);
    await migratePaymentSessions(db);
    await migrateOtpSessions(db);
    await migratePasswordResetSessions(db);
    await migrateAdminSettings(db);
    // eslint-disable-next-line no-console
    console.info("MongoDB -> MySQL migration completed.");
  } finally {
    await client.close();
    await prisma.$disconnect();
  }
}

await main();
