const MONDAY_API = "https://api.monday.com/v2";
const MONDAY_FILE_API = "https://api.monday.com/v2/file";

type MondayColumn = { id: string; title: string; type: string };

async function gql(token: string, query: string, variables?: Record<string, unknown>) {
  const body = JSON.stringify({ query, variables });
  console.log("[monday gql] request:", body.slice(0, 500));

  const res = await fetch(MONDAY_API, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: token,
      "API-Version": "2024-10",
    },
    body,
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Monday.com API ${res.status}: ${text}`);
  }
  const json = await res.json();
  if (json.errors?.length) {
    const err = json.errors[0];
    const detail = err.extensions ? ` (${JSON.stringify(err.extensions)})` : "";
    console.error("[monday gql] GraphQL error:", JSON.stringify(json.errors));
    throw new Error(`Monday.com GraphQL: ${err.message}${detail}`);
  }
  if (json.error_message) {
    console.error("[monday gql] API error:", json.error_message, json.error_code);
    throw new Error(`Monday.com: ${json.error_message} (${json.error_code || "unknown"})`);
  }
  return json.data;
}

export async function fetchBoardColumns(token: string, boardId: string): Promise<MondayColumn[]> {
  const data = await gql(token, `
    query ($boardId: [ID!]!) {
      boards(ids: $boardId) {
        columns { id title type }
      }
    }
  `, { boardId: [boardId] });
  return data?.boards?.[0]?.columns ?? [];
}

export async function createItem(
  token: string,
  boardId: string,
  itemName: string,
  columnValues: Record<string, unknown>
): Promise<string> {
  const data = await gql(token, `
    mutation ($boardId: ID!, $itemName: String!, $columnValues: JSON!) {
      create_item(
        board_id: $boardId,
        item_name: $itemName,
        column_values: $columnValues,
        create_labels_if_missing: true
      ) { id }
    }
  `, {
    boardId,
    itemName,
    columnValues: JSON.stringify(columnValues),
  });
  return String(data.create_item.id);
}

export async function updateItem(
  token: string,
  boardId: string,
  itemId: string,
  columnValues: Record<string, unknown>
): Promise<void> {
  await gql(token, `
    mutation ($boardId: ID!, $itemId: ID!, $columnValues: JSON!) {
      change_multiple_column_values(
        board_id: $boardId,
        item_id: $itemId,
        column_values: $columnValues,
        create_labels_if_missing: true
      ) { id }
    }
  `, {
    boardId,
    itemId,
    columnValues: JSON.stringify(columnValues),
  });
}

export async function uploadFileToItem(
  token: string,
  itemId: string,
  columnId: string,
  fileBuffer: Buffer,
  fileName: string
): Promise<void> {
  const query = `mutation ($file: File!) { add_file_to_column(file: $file, item_id: ${itemId}, column_id: "${columnId}") { id } }`;

  const form = new FormData();
  form.append("query", query);
  form.append("variables[file]", new Blob([new Uint8Array(fileBuffer)]), fileName);

  const res = await fetch(MONDAY_FILE_API, {
    method: "POST",
    headers: { Authorization: token },
    body: form,
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Monday.com file upload ${res.status}: ${text}`);
  }
}

type ItemColumnValue = { id: string; text: string; value: string | null };

export async function readItem(
  token: string,
  itemId: string
): Promise<{ name: string; columnValues: ItemColumnValue[] } | null> {
  const data = await gql(token, `
    query ($itemId: [ID!]!) {
      items(ids: $itemId) {
        name
        column_values { id text value }
      }
    }
  `, { itemId: [itemId] });
  const item = data?.items?.[0];
  if (!item) return null;
  return {
    name: item.name,
    columnValues: item.column_values.map((cv: { id: string; text: string; value: string | null }) => ({
      id: cv.id,
      text: cv.text,
      value: cv.value,
    })),
  };
}

function toCleanString(val: unknown): string | null {
  if (val === null || val === undefined) return null;
  if (Array.isArray(val)) {
    const joined = val.filter(Boolean).join(", ");
    return joined || null;
  }
  if (typeof val === "object") {
    const s = JSON.stringify(val);
    return s === "{}" || s === "[]" ? null : s;
  }
  const str = String(val).trim();
  if (str === "" || str === "null" || str === "undefined" || str === "[object Object]") return null;
  return str;
}

const DROPDOWN_NORMALIZATIONS: [RegExp, string][] = [
  [/expressing my faith in jesus/i, "Baptism"],
  [/i want to be baptized/i, "Baptism"],
  [/learning more about becoming a partner/i, "Next Steps"],
  [/attend next steps/i, "Next Steps"],
];

function normalizeDropdownLabel(label: string): string {
  for (const [pattern, replacement] of DROPDOWN_NORMALIZATIONS) {
    if (pattern.test(label)) return replacement;
  }
  return label;
}

function formatForColumnType(colType: string, val: unknown): unknown | null {
  switch (colType) {
    case "boolean":
    case "checkbox": {
      const isChecked = val === true || val === "true";
      return { checked: isChecked ? "true" : "false" };
    }

    case "color":
    case "status": {
      const str = toCleanString(val);
      return str ? { label: str } : null;
    }

    case "dropdown": {
      const str = toCleanString(val);
      if (!str) return null;
      const labels = str.split(",").map((s) => normalizeDropdownLabel(s.trim())).filter(Boolean);
      return labels.length > 0 ? { labels } : null;
    }

    case "date": {
      const str = toCleanString(val);
      if (!str) return null;
      if (typeof val === "object" && !Array.isArray(val) && (val as Record<string, unknown>).date) {
        const inner = String((val as Record<string, unknown>).date);
        const iso = parseToISO(inner);
        return iso ? { date: iso } : null;
      }
      const iso = parseToISO(str);
      return iso ? { date: iso } : null;
    }

    case "numeric":
    case "numbers": {
      const str = toCleanString(val);
      if (!str) return null;
      const num = parseFloat(str);
      return isNaN(num) ? null : String(num);
    }

    case "email": {
      const str = toCleanString(val);
      return str ? { email: str, text: str } : null;
    }

    case "phone": {
      const str = toCleanString(val);
      return str ? { phone: str, countryShortName: "US" } : null;
    }

    case "link": {
      const str = toCleanString(val);
      return str ? { url: str, text: str } : null;
    }

    default: {
      return toCleanString(val);
    }
  }
}

const MONTH_MAP: Record<string, string> = {
  jan: "01", january: "01", feb: "02", february: "02",
  mar: "03", march: "03", apr: "04", april: "04",
  may: "05", jun: "06", june: "06", jul: "07", july: "07",
  aug: "08", august: "08", sep: "09", september: "09",
  oct: "10", october: "10", nov: "11", november: "11",
  dec: "12", december: "12",
};

function normalizeYear(y: string): string {
  if (y.length === 4) return y;
  const n = parseInt(y);
  return n > 50 ? `19${y.padStart(2, "0")}` : `20${y.padStart(2, "0")}`;
}

function parseToISO(dateStr: string): string | null {
  const s = dateStr.replace(/\s+/g, " ").trim();

  // Full ISO datetime e.g. "2026-04-06T00:00:00.000Z"
  const isoFull = s.match(/^(\d{4})-(\d{2})-(\d{2})T/);
  if (isoFull) return `${isoFull[1]}-${isoFull[2]}-${isoFull[3]}`;

  // MM/DD/YYYY or DD/MM/YYYY (all-numeric)
  const numParts = s.match(/^(\d{1,2})\s*[/\-.]\s*(\d{1,2})\s*[/\-.]\s*(\d{2,4})$/);
  if (numParts) {
    let [, a, b, y] = numParts;
    y = normalizeYear(y);
    const aNum = parseInt(a), bNum = parseInt(b);
    let month: string, day: string;
    if (aNum > 12 && bNum <= 12) {
      day = a; month = b;
    } else {
      month = a; day = b;
    }
    return `${y}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
  }

  // DD/Mon/YYYY  e.g. "20/May/1999", "08/JUN/26"
  const dmyNamed = s.match(/^(\d{1,2})\s*[/\-.]\s*([A-Za-z]+)\s*[/\-.]\s*(\d{2,4})$/);
  if (dmyNamed) {
    const [, d, mName, y] = dmyNamed;
    const month = MONTH_MAP[mName.toLowerCase()];
    if (month) return `${normalizeYear(y)}-${month}-${d.padStart(2, "0")}`;
  }

  // "Month DD, YY(YY)" e.g. "july 20, 02", "Feb 19, 2016", "October 5, 1990"
  const mdyNamed = s.match(/^([A-Za-z]+)\s+(\d{1,2}),?\s*(\d{2,4})$/);
  if (mdyNamed) {
    const [, mName, d, y] = mdyNamed;
    const month = MONTH_MAP[mName.toLowerCase()];
    if (month) return `${normalizeYear(y)}-${month}-${d.padStart(2, "0")}`;
  }

  // "Mon / DD / YYYY" with spaces e.g. "Oct / 02 / 2004"
  const spacedNamed = s.match(/^([A-Za-z]+)\s*[/\-.]\s*(\d{1,2})\s*[/\-.]\s*(\d{2,4})$/);
  if (spacedNamed) {
    const [, mName, d, y] = spacedNamed;
    const month = MONTH_MAP[mName.toLowerCase()];
    if (month) return `${normalizeYear(y)}-${month}-${d.padStart(2, "0")}`;
  }

  // YYYY-MM-DD already ISO
  const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (iso) return s;

  // Last resort: try native Date parsing
  const d = new Date(s);
  if (!isNaN(d.getTime()) && d.getFullYear() > 1900) {
    const yyyy = String(d.getFullYear());
    const mm = String(d.getMonth() + 1).padStart(2, "0");
    const dd = String(d.getDate()).padStart(2, "0");
    return `${yyyy}-${mm}-${dd}`;
  }

  return null;
}

export function mapCardToColumnValues(
  card: Record<string, unknown>,
  columnMap: Record<string, unknown>
): Record<string, unknown> {
  const values: Record<string, unknown> = {};
  const columnTypes = ((columnMap._columnTypes as Record<string, string>) ?? {});

  console.log("[mapCardToColumnValues] _columnTypes keys:", Object.keys(columnTypes).length, "sample:", JSON.stringify(Object.entries(columnTypes).slice(0, 5)));

  for (const [cardField, rawColId] of Object.entries(columnMap)) {
    if (!rawColId || !cardField || cardField.startsWith("_") || typeof rawColId !== "string") continue;
    const colId = rawColId;
    const val = card[cardField];
    if (val === null || val === undefined) continue;

    const colType = columnTypes[colId] || "text";
    const formatted = formatForColumnType(colType, val);
    if (formatted !== null) {
      values[colId] = formatted;
    }
  }

  return values;
}

export function mapItemToCardFields(
  columnValues: ItemColumnValue[],
  columnMap: Record<string, unknown>
): Record<string, string> {
  const reverseMap: Record<string, string> = {};
  for (const [cardField, colId] of Object.entries(columnMap)) {
    if (cardField.startsWith("_") || typeof colId !== "string") continue;
    reverseMap[colId] = cardField;
  }

  const cardData: Record<string, string> = {};
  for (const cv of columnValues) {
    const cardField = reverseMap[cv.id];
    if (cardField && cv.text) {
      cardData[cardField] = cv.text;
    }
  }
  return cardData;
}

export async function createWebhookSubscription(
  token: string,
  boardId: string,
  callbackUrl: string
): Promise<string> {
  const escapedUrl = callbackUrl.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  const data = await gql(token, `
    mutation {
      create_webhook(board_id: ${boardId}, url: "${escapedUrl}", event: change_column_value) {
        id
        board_id
      }
    }
  `);
  return String(data.create_webhook.id);
}

export async function deleteWebhookSubscription(
  token: string,
  webhookId: string
): Promise<void> {
  await gql(token, `
    mutation {
      delete_webhook(id: ${webhookId}) {
        id
      }
    }
  `);
}
