import { logError, logInfo } from "@/lib/logger";
import type { Kysely } from "kysely";
import { sql } from "kysely";
import { listKcgmBookings, type KcgmBookingRow } from "./kcgm-bookings.repo";
import type { ViewpointDB } from "./types";

export const SOURCE_OF_TRUTH_DB = "db";

const MEMBERSHIP_SOURCE_COLUMN: keyof Pick<
  KcgmBookingRow,
  "account_id" | "entitle_account_id"
> = "entitle_account_id";

export interface KcgmSyncOutcome {
  read: number;
  written: number;
  failed: number;
  errors: Array<{ bookingNo: string; message: string }>;
}

const PAGE_SIZE = 500;

const UPSERT_COLUMNS = [
  "status",
  "booking_source",
  "booking_type",
  "account_id",
  "membership_number",
  "guest_name",
  "resort_name",
  "room_type",
  "arrival",
  "departure",
  "adults",
  "children",
  "booking_date",
  "total_amount",
  "currency",
  "modify_date_time",
  "source_of_truth",
  "last_synced_at",
] as const;

const toDateOnly = (value: Date | null): string | null =>
  value ? value.toISOString().slice(0, 10) : null;

export async function syncKcgmBookings(
  db: Kysely<any>,
  viewpointDb: Kysely<ViewpointDB>,
): Promise<KcgmSyncOutcome> {
  const outcome: KcgmSyncOutcome = {
    read: 0,
    written: 0,
    failed: 0,
    errors: [],
  };

  let offset = 0;
  for (;;) {
    const page = await listKcgmBookings(viewpointDb, {
      limit: PAGE_SIZE,
      offset,
    });
    if (page.length === 0) break;
    outcome.read += page.length;

    for (const row of page) {
      const bookingNo = String(row.booking_no);
      try {
        await db
          .insertInto("viewpoint_bookings")
          .values({
            booking_no: bookingNo,
            status: row.status,
            booking_source: row.booking_source,
            booking_type: row.booking_type,
            account_id: row.account_id,
            membership_number: row[MEMBERSHIP_SOURCE_COLUMN],
            guest_name: row.guest_name,
            resort_name: row.resort_name,
            room_type: row.room_type,
            arrival: toDateOnly(row.arrival),
            departure: toDateOnly(row.departure),
            adults: row.adults,
            children: row.children,
            booking_date: toDateOnly(row.confirmed_date),
            total_amount: row.folio_total,
            currency: row.currency,
            modify_date_time: row.modify_date,
            source_of_truth: SOURCE_OF_TRUTH_DB,
            last_synced_at: new Date(),
            sync_error: null,
          })
          .onConflict((oc) =>
            oc.column("booking_no").doUpdateSet({
              ...Object.fromEntries(
                UPSERT_COLUMNS.map((column) => [
                  column,
                  sql.ref(`excluded.${column}`),
                ]),
              ),
              sync_error: null,
              updated_at: sql`now()`,
            }),
          )
          .execute();
        outcome.written += 1;
      } catch (err) {
        outcome.failed += 1;
        const message = err instanceof Error ? err.message : String(err);
        outcome.errors.push({ bookingNo, message });
        logError(`[KCGM sync] booking ${bookingNo} failed: ${message}`);
      }
    }

    if (page.length < PAGE_SIZE) break;
    offset += PAGE_SIZE;
  }

  logInfo(
    `[KCGM sync] read ${outcome.read}, written ${outcome.written}, failed ${outcome.failed}`,
  );
  return outcome;
}
