const query = require("../db.js");

const WORK_ORDER_TIMESTAMP_FIELDS = new Map([
  ["Request date", "request_date"],
  ["Request date 2", "request_date_2"],
  ["Created", "created"],
  ["Start", "start"],
  ["WO Accepted By Vendor", "wo_accepted_by_vendor"],
  ["Start Work", "start_work"],
  ["Downtime start date", "downtime_start_date"],
  ["Downtime end", "downtime_end"],
  ["Note closed time", "note_closed_time"],
  ["End Work", "end_work"],
  ["End", "end"],
  ["Closed", "closed"],
]);

const WORK_ORDER_FIELD_ALIASES = new Map([
  ["Address", "address"],
  ["Assignee", "assignee"],
  ["Assignee profile", "assignee_profile"],
  ["Bookmark", "bookmark"],
  ["Category", "category"],
  ["Contact", "contact"],
  ["District", "district"],
  ["Division manager", "division_manager"],
  ["Downtime Cat.", "downtime_cat"],
  ["Downtime?", "downtime"],
  ["From", "from_requestor"],
  ["Labor", "labor_amount"],
  ["Qty. Labor Hours", "labor_hours"],
  ["Location", "location"],
  ["Material", "material_amount"],
  ["NTE Amt. $", "nte_amount"],
  ["NTE Amount", "nte_amount_2"],
  ["Part of", "part_of"],
  ["Priority", "priority"],
  ["mainProblem", "problem"],
  ["Problem Type", "problemtype"],
  ["Property", "property"],
  ["Reference", "reference"],
  ["Site #", "site_number"],
  ["State", "state"],
  ["Status", "status"],
  ["Subject", "subject"],
  ["Total", "total_amount"],
  ["Travel", "travel_amount"],
  ["Travel count", "travel_count"],
  ["Type", "type"],
  ["VAT", "vat"],
  ["Vendor Response Time", "vendor_response_time"],
]);

function normalizeIdentifier(value) {
  return String(value)
    .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
    .replace(/[^a-zA-Z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .replace(/_+/g, "_")
    .toLowerCase();
}

function quoteIdentifier(value) {
  return `"${String(value).replace(/"/g, "\"\"")}"`;
}

function quoteLiteral(value) {
  return `'${String(value).replace(/'/g, "''")}'`;
}

function getColumnAlias(key) {
  return WORK_ORDER_TIMESTAMP_FIELDS.get(key)
    || WORK_ORDER_FIELD_ALIASES.get(key)
    || normalizeIdentifier(key);
}

function buildTimestampExpression(tableName, key, alias) {
  return `
        CASE
            WHEN ((${tableName}.data ->> ${quoteLiteral(key)}) = ''::text) THEN NULL::timestamp with time zone
            ELSE to_timestamp((${tableName}.data ->> ${quoteLiteral(key)}), 'YYYY-MM-DD"T"HH24:MI:SS'::text)
        END AS ${quoteIdentifier(alias)}`;
}

function buildTextExpression(tableName, key, alias) {
  return `(${tableName}.data ->> ${quoteLiteral(key)}) AS ${quoteIdentifier(alias)}`;
}

async function ensureTableExists(tableName) {
  await query(`
    CREATE TABLE IF NOT EXISTS ${tableName} (
      id text primary key,
      created_at timestamp with time zone not null default now(),
      updated_at timestamp with time zone not null default now(),
      data jsonb not null
    )`);

  await query(`
    DROP TRIGGER IF EXISTS set_update_${tableName} ON ${tableName}`);

  await query(`
    CREATE TRIGGER set_update_${tableName}
      BEFORE UPDATE ON ${tableName}
      FOR EACH ROW
      EXECUTE PROCEDURE trigger_set_timestamp()`);
}

async function getFlattenedKeys(tableName) {
  const result = await query(`
    SELECT DISTINCT key
    FROM ${tableName},
    LATERAL jsonb_object_keys(data) AS key
    ORDER BY key`);

  return result.rows
    .map((row) => row.key)
    .filter((key) => !["id", "created_at", "updated_at", "data"].includes(key));
}

async function refreshFlattenedView(tableName, viewName) {
  const keys = await getFlattenedKeys(tableName);
  const seenAliases = new Set(["id", "created_at", "updated_at", "data"]);

  const selectFragments = [
    `${tableName}.id`,
    `${tableName}.created_at`,
    `${tableName}.updated_at`,
  ];

  for (const key of keys) {
    let alias = getColumnAlias(key);
    if (!alias) {
      continue;
    }

    let suffix = 2;
    while (seenAliases.has(alias)) {
      alias = `${alias}_${suffix}`;
      suffix += 1;
    }
    seenAliases.add(alias);

    const expression = WORK_ORDER_TIMESTAMP_FIELDS.has(key)
      ? buildTimestampExpression(tableName, key, alias)
      : buildTextExpression(tableName, key, alias);

    selectFragments.push(expression);
  }

  await query(`
    DROP VIEW IF EXISTS ${viewName}`);

  await query(`
    CREATE VIEW ${viewName} AS
    SELECT ${selectFragments.join(",\n    ")}
    FROM ${tableName}`);
}

const addData = async (req, res) => {
  try {
    const client = req.params.client;
    const { tableName, data } = req.body;

    if (typeof tableName !== "string" || !tableName.trim()) {
      return res.status(400).json({ error: "tableName is required." });
    }

    if (!Array.isArray(data)) {
      return res.status(400).json({ error: "data must be an array." });
    }

    const normalizedClient = normalizeIdentifier(client);
    const normalizedTableName = normalizeIdentifier(tableName);

    if (!normalizedClient || !normalizedTableName) {
      return res.status(400).json({ error: "Invalid client or tableName." });
    }

    const dBtableName = `${normalizedClient}_${normalizedTableName}`;
    const flattenedViewName = `${normalizedClient}_flattened_${normalizedTableName}`;
    const insertQuery = `
      INSERT INTO ${dBtableName} (id, data) VALUES ($1, $2)
      ON CONFLICT (id) DO UPDATE SET data = $2`;

    for (const item of data) {
      if (!item || typeof item !== "object" || item.id == null) {
        return res.status(400).json({ error: "Each data item must be an object with an id." });
      }
    }

    await query("BEGIN");

    try {
      await ensureTableExists(dBtableName);

      for (const item of data) {
        await query(insertQuery, [String(item.id), item]);
      }

      await refreshFlattenedView(dBtableName, flattenedViewName);
      await query("COMMIT");
    } catch (err) {
      await query("ROLLBACK");
      throw err;
    }

    res.status(200).json({
      message: "Data added successfully",
      tableName: dBtableName,
      viewName: flattenedViewName,
    });

  } catch (err) {
    console.error("Error handling the request:", err);
    res
      .status(500)
      .json({ error: "An error occurred while processing the request" });
  }
};

module.exports = addData;
