/**
 * Bookings Domain Adapter
 *
 * Routes demo_bookings CRUD to Prisma or Convex based on data-source config.
 * Maintains Prisma-compatible method signatures so service layer changes are minimal.
 *
 * Covers:
 * - demo_bookings table (the primary booking entity for demos)
 * - All Prisma calls in booking.service.ts, demo-reminder.service.ts, and API routes
 *
 * Timestamp fields: createdAt, updatedAt, appointmentDate, reminderSentAt,
 *   webhookLastAttempt, contactedAt, depositPaidAt, depositReminderSentAt
 */

import { prisma } from "@/lib/db";
import { convexQuery, convexMutation } from "@/lib/convex-client";
import { getDataSource } from "@/lib/data-source";
import {
  convexToPrismaShape,
  convexToPrismaShapeMany,
  applyPagination,
  dateToConvexTimestamp,
  type ConvexDoc,
  type BatchResult,
} from "./base";

const DOMAIN = "bookings" as const;
const TIMESTAMP_FIELDS = [
  "createdAt",
  "updatedAt",
  "appointmentDate",
  "reminderSentAt",
  "webhookLastAttempt",
  "contactedAt",
  "depositPaidAt",
  "depositReminderSentAt",
];

function isConvex(): boolean {
  return getDataSource(DOMAIN) === "convex";
}

// ─── findMany ────────────────────────────────────────────────────────────

export async function findMany(args: {
  where?: Record<string, unknown>;
  orderBy?: Record<string, string> | Record<string, string>[];
  take?: number;
  skip?: number;
  select?: Record<string, boolean>;
}) {
  if (!isConvex()) {
    return prisma.demo_bookings.findMany(args as any);
  }

  const where = (args.where || {}) as Record<string, unknown>;
  const status = where.status as
    | string
    | { in?: string[]; notIn?: string[] }
    | undefined;
  const email = where.email as
    | string
    | { contains?: string; mode?: string }
    | undefined;

  // Determine Convex query parameters
  let convexStatus: string | undefined;
  if (typeof status === "string") {
    convexStatus = status;
  }

  // Date range extraction
  let startDate: number | undefined;
  let endDate: number | undefined;
  const appointmentDate = where.appointmentDate as
    | { gte?: Date; lte?: Date; lt?: Date }
    | Date
    | undefined;
  if (
    appointmentDate &&
    typeof appointmentDate === "object" &&
    !(appointmentDate instanceof Date)
  ) {
    if (appointmentDate.gte)
      startDate = dateToConvexTimestamp(appointmentDate.gte);
    if (appointmentDate.lte)
      endDate = dateToConvexTimestamp(appointmentDate.lte);
    if (appointmentDate.lt) endDate = dateToConvexTimestamp(appointmentDate.lt);
  }

  const overFetchLimit = (args.take || 100) + (args.skip || 0);

  // Use date-range query if date filters are provided
  let docs: ConvexDoc[];
  if (startDate !== undefined || endDate !== undefined) {
    docs =
      (await convexQuery<ConvexDoc[]>(
        "bookings/queries:getDemoBookingsByDate",
        {
          startDate: startDate ?? 0,
          endDate: endDate ?? Date.now() + 365 * 24 * 60 * 60 * 1000,
        },
      )) || [];
  } else {
    docs =
      (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
        ...(convexStatus ? { status: convexStatus } : {}),
        limit: overFetchLimit,
      })) || [];
  }

  // Apply in-memory filters that Convex doesn't handle natively
  let filtered = docs;

  // Filter by status.in or status.notIn
  if (status && typeof status === "object") {
    if (status.in) {
      const allowed = new Set(status.in);
      filtered = filtered.filter((d) => allowed.has(d.status as string));
    }
    if (status.notIn) {
      const blocked = new Set(status.notIn);
      filtered = filtered.filter((d) => !blocked.has(d.status as string));
    }
  }

  // Filter by email (simple equality or contains)
  if (email) {
    if (typeof email === "string") {
      filtered = filtered.filter(
        (d) => (d.email as string)?.toLowerCase() === email.toLowerCase(),
      );
    } else if (email.contains) {
      const search = email.contains.toLowerCase();
      filtered = filtered.filter((d) =>
        (d.email as string)?.toLowerCase().includes(search),
      );
    }
  }

  // Filter: reminderSentAt is null
  if (where.reminderSentAt === null) {
    filtered = filtered.filter((d) => d.reminderSentAt == null);
  }

  // Filter: googleJoinUrl not null
  const googleJoinUrlFilter = where.googleJoinUrl as { not?: null } | undefined;
  if (googleJoinUrlFilter && googleJoinUrlFilter.not === null) {
    filtered = filtered.filter((d) => d.googleJoinUrl != null);
  }

  // Apply pagination
  const paginated = applyPagination(filtered, args.skip, args.take);

  return convexToPrismaShapeMany(paginated, TIMESTAMP_FIELDS);
}

// ─── findFirst ───────────────────────────────────────────────────────────

export async function findFirst(args: { where?: Record<string, unknown> }) {
  if (!isConvex()) {
    return prisma.demo_bookings.findFirst(args as any);
  }

  const where = (args.where || {}) as Record<string, unknown>;

  // For duplicate check: email + appointmentDate + appointmentTime + status filter
  const emailVal = where.email as string | undefined;
  const appointmentTime = where.appointmentTime as string | undefined;
  const appointmentDate = where.appointmentDate as Date | undefined;
  const statusFilter = where.status as
    | { notIn?: string[] }
    | string
    | undefined;

  // Over-fetch from Convex and filter in memory
  let docs: ConvexDoc[];
  if (emailVal) {
    docs =
      (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
        limit: 1000,
      })) || [];
  } else {
    docs =
      (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
        limit: 1000,
      })) || [];
  }

  // Match by all where conditions
  const match = docs.find((doc) => {
    for (const [field, value] of Object.entries(where)) {
      const docVal = field === "id" ? doc.prismaId || doc._id : doc[field];

      // Handle Date equality
      if (value instanceof Date) {
        const docTimestamp =
          typeof docVal === "number"
            ? docVal
            : docVal instanceof Date
              ? docVal.getTime()
              : null;
        if (docTimestamp !== value.getTime()) return false;
        continue;
      }

      // Handle { notIn: [...] }
      if (
        value &&
        typeof value === "object" &&
        "notIn" in (value as Record<string, unknown>)
      ) {
        const notIn = (value as { notIn: string[] }).notIn;
        if (notIn.includes(docVal as string)) return false;
        continue;
      }

      // Simple equality
      if (docVal !== value) return false;
    }
    return true;
  });

  return match ? convexToPrismaShape(match, TIMESTAMP_FIELDS) : null;
}

// ─── findUnique ──────────────────────────────────────────────────────────

export async function findUnique(args: {
  where: { id: string };
  select?: Record<string, boolean>;
}) {
  if (!isConvex()) {
    return prisma.demo_bookings.findUnique(args as any);
  }

  // Convex doesn't have a single-doc query for demo_bookings, use findFirst
  return findFirst({ where: args.where });
}

// ─── count ───────────────────────────────────────────────────────────────

export async function count(args?: { where?: Record<string, unknown> }) {
  if (!isConvex()) {
    return prisma.demo_bookings.count(args as any);
  }

  const where = (args?.where || {}) as Record<string, unknown>;
  const status = where.status as string | undefined;

  // Fetch all matching docs and count
  const docs =
    (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
      ...(status ? { status } : {}),
      limit: 10000,
    })) || [];

  // Apply additional where filters
  let filtered = docs;

  // Date range
  const appointmentDate = where.appointmentDate as
    | { gte?: Date; lte?: Date }
    | undefined;
  if (appointmentDate && typeof appointmentDate === "object") {
    if (appointmentDate.gte) {
      const gteMs = appointmentDate.gte.getTime();
      filtered = filtered.filter((d) => (d.appointmentDate as number) >= gteMs);
    }
    if (appointmentDate.lte) {
      const lteMs = appointmentDate.lte.getTime();
      filtered = filtered.filter((d) => (d.appointmentDate as number) <= lteMs);
    }
  }

  // Email contains
  const emailFilter = where.email as
    | { contains?: string; mode?: string }
    | undefined;
  if (emailFilter && typeof emailFilter === "object" && emailFilter.contains) {
    const search = emailFilter.contains.toLowerCase();
    filtered = filtered.filter((d) =>
      (d.email as string)?.toLowerCase().includes(search),
    );
  }

  return filtered.length;
}

// ─── create ──────────────────────────────────────────────────────────────

export async function create(args: {
  data: {
    id?: string;
    fullName: string;
    email: string;
    telephone: string;
    appointmentDate: Date;
    appointmentTime: string;
    duration: string;
    specialRequirements?: string | null;
    language: string;
    customerTimezone?: string | null;
    status?: string;
    webhookStatus?: string;
    webhookRetryCount?: number;
    createdAt?: Date;
    updatedAt?: Date;
    [field: string]: unknown;
  };
}) {
  if (!isConvex()) {
    return prisma.demo_bookings.create(args as any);
  }

  const d = args.data;
  const convexId = await convexMutation<string>(
    "bookings/mutations:createDemoBooking",
    {
      fullName: d.fullName,
      email: d.email,
      telephone: d.telephone,
      appointmentDate: dateToConvexTimestamp(d.appointmentDate) ?? Date.now(),
      appointmentTime: d.appointmentTime,
      duration: d.duration,
      ...(d.specialRequirements
        ? { specialRequirements: d.specialRequirements }
        : {}),
      language: d.language,
      ...(d.status ? { status: d.status } : {}),
      ...(d.webhookStatus ? { webhookStatus: d.webhookStatus } : {}),
      ...(d.webhookRetryCount !== undefined
        ? { webhookRetryCount: d.webhookRetryCount }
        : {}),
      ...(d.customerTimezone ? { customerTimezone: d.customerTimezone } : {}),
    },
  );

  // Return Prisma-compatible shape
  const now = d.createdAt || new Date();
  return {
    id: d.id || convexId || "",
    fullName: d.fullName,
    email: d.email,
    telephone: d.telephone,
    appointmentDate: d.appointmentDate,
    appointmentTime: d.appointmentTime,
    duration: d.duration,
    specialRequirements: d.specialRequirements ?? null,
    language: d.language,
    customerTimezone: d.customerTimezone ?? null,
    status: d.status ?? "pending",
    webhookStatus: d.webhookStatus ?? "pending",
    webhookRetryCount: d.webhookRetryCount ?? 0,
    meetingLink: null,
    googleCalendarEventId: null,
    googleConferenceId: null,
    googleJoinUrl: null,
    reminderSentAt: null,
    reminderEmailSent: false,
    reminderWhatsAppSent: false,
    reminderStatus: null,
    reminderError: null,
    createdAt: now,
    updatedAt: d.updatedAt || now,
  };
}

// ─── update ──────────────────────────────────────────────────────────────

export async function update(args: {
  where: { id: string };
  data: Record<string, unknown>;
}) {
  if (!isConvex()) {
    return prisma.demo_bookings.update(args as any);
  }

  const bookingId = args.where.id;
  const data = args.data;

  // Route to appropriate Convex mutation based on what's being updated
  if (data.status !== undefined) {
    // Status update — use generic updateStatus or cancel mutation
    if (data.status === "cancelled") {
      await convexMutation("bookings/mutations:cancel", {
        id: bookingId,
        ...(data.specialRequirements
          ? { reason: data.specialRequirements as string }
          : {}),
      }).catch(() => {
        // cancel expects v.id("bookings"), but demo_bookings IDs differ.
        // Fall through to calendar-data mutation for generic updates.
      });
    }
    // For other status changes, use the calendar-data mutation as a generic patcher
    await convexMutation("bookings/mutations:updateDemoBookingCalendarData", {
      id: bookingId,
    }).catch(() => {
      // Best-effort — Convex may not have a generic demo_bookings update
    });
  }

  // Meeting link / calendar data update
  if (
    data.meetingLink !== undefined ||
    data.googleCalendarEventId !== undefined ||
    data.googleConferenceId !== undefined ||
    data.googleJoinUrl !== undefined
  ) {
    await convexMutation("bookings/mutations:updateDemoBookingCalendarData", {
      id: bookingId,
      ...(data.meetingLink !== undefined
        ? { meetingLink: data.meetingLink as string }
        : {}),
      ...(data.googleCalendarEventId !== undefined
        ? { googleCalendarEventId: data.googleCalendarEventId as string }
        : {}),
      ...(data.googleConferenceId !== undefined
        ? { googleConferenceId: data.googleConferenceId as string }
        : {}),
      ...(data.googleJoinUrl !== undefined
        ? { googleJoinUrl: data.googleJoinUrl as string }
        : {}),
    });
  }

  // Return merged shape (best-effort since Convex mutations don't return docs)
  const existing = await findFirst({ where: { id: bookingId } });
  return existing ? { ...existing, ...data } : null;
}

// ─── updateMany ──────────────────────────────────────────────────────────

export async function updateMany(args: {
  where: Record<string, unknown>;
  data: Record<string, unknown>;
}): Promise<BatchResult> {
  if (!isConvex()) {
    return prisma.demo_bookings.updateMany(args as any);
  }

  // Fetch matching docs
  const docs =
    (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
      limit: 10000,
    })) || [];

  // Filter by where conditions
  let matching = docs;
  const where = args.where;

  if (where.status) {
    if (typeof where.status === "string") {
      matching = matching.filter((d) => d.status === where.status);
    } else if (typeof where.status === "object") {
      const statusIn = (where.status as { in?: string[] }).in;
      if (statusIn) {
        const allowed = new Set(statusIn);
        matching = matching.filter((d) => allowed.has(d.status as string));
      }
    }
  }

  const inIds = (where.id as { in?: string[] })?.in;
  if (inIds) {
    const idSet = new Set(inIds);
    matching = matching.filter((d) =>
      idSet.has((d.prismaId as string) || d._id),
    );
  }

  // Update each matching doc
  let updatedCount = 0;
  for (const doc of matching) {
    try {
      await update({
        where: { id: doc._id },
        data: args.data,
      });
      updatedCount++;
    } catch {
      // Continue on individual failures
    }
  }

  return { count: updatedCount };
}

// ─── delete ──────────────────────────────────────────────────────────────

async function deleteOne(args: { where: { id: string } }) {
  if (!isConvex()) {
    return prisma.demo_bookings.delete(args as any);
  }

  // Convex doesn't have a dedicated delete for demo_bookings.
  // Cancel the booking instead (soft delete).
  await convexMutation("bookings/mutations:cancel", {
    id: args.where.id,
    reason: "Deleted via adapter",
  }).catch(() => {
    // Ignore if cancel fails — different table schema
  });

  return { id: args.where.id };
}

export { deleteOne as delete };

// ─── deleteMany ──────────────────────────────────────────────────────────

export async function deleteMany(args: {
  where: Record<string, unknown>;
}): Promise<BatchResult> {
  if (!isConvex()) {
    return prisma.demo_bookings.deleteMany(args as any);
  }

  // Fetch matching docs
  const docs =
    (await convexQuery<ConvexDoc[]>("bookings/queries:getDemoBookings", {
      limit: 10000,
    })) || [];

  // Filter
  let matching = docs;
  const where = args.where;

  const inIds = (where.id as { in?: string[] })?.in;
  if (inIds) {
    const idSet = new Set(inIds);
    matching = docs.filter((d) => idSet.has((d.prismaId as string) || d._id));
  }
  if (where.status) {
    matching = matching.filter((d) => d.status === where.status);
  }

  // Delete each
  for (const doc of matching) {
    await convexMutation("bookings/mutations:cancel", {
      id: doc._id,
      reason: "Bulk delete via adapter",
    }).catch(() => {
      // Continue
    });
  }

  return { count: matching.length };
}

// ═══════════════════════════════════════════════════════════════════════════
// Booking Notes
// ═══════════════════════════════════════════════════════════════════════════

export const bookingNotesAdapter = {
  async findFirst(args: { where?: Record<string, unknown> }) {
    return prisma.booking_notes.findFirst(args as never);
  },

  async findMany(args: {
    where?: Record<string, unknown>;
    orderBy?: Record<string, unknown>;
    take?: number;
    skip?: number;
  }) {
    return prisma.booking_notes.findMany(args as never);
  },

  async create(args: { data: Record<string, unknown> }) {
    return prisma.booking_notes.create(args as never);
  },

  async update(args: {
    where: Record<string, unknown>;
    data: Record<string, unknown>;
  }) {
    return prisma.booking_notes.update(args as never);
  },

  async delete(args: { where: Record<string, unknown> }) {
    return prisma.booking_notes.delete(args as never);
  },
};

// ─── Namespace export ────────────────────────────────────────────────────

export const bookingsAdapter = {
  findMany,
  findFirst,
  findUnique,
  count,
  create,
  update,
  updateMany,
  delete: deleteOne,
  deleteMany,
};
