import fs from "node:fs/promises";
import crypto from "node:crypto";
import path from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";


let FileBlob;
let SpreadsheetFile;
let Workbook;


const EXPECTED_HEADERS = [
  "ARRIVAL",
  "DEPARTURE",
  "NIGHTS",
  "BLOCK_CODE",
  "RES_COMMENT",
  "Booking Room",
  "Total Booking Price",
];

const DUPLICATE_FILL = "#FFF2CC";
const DUPLICATE_FONT = "#9C6500";
const REVIEW_FILL = "#FCE4D6";
const REVIEW_FONT = "#C65911";
const HEADER_FILL = "#FFFFFF";
const BODY_FONT = "#222222";
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;


async function loadArtifactTool() {
  const configured = String(process.env.COMPANY_REPORT_ARTIFACT_TOOL_MODULE ?? "").trim();
  const module = configured
    ? await import(pathToFileURL(path.resolve(configured)).href)
    : await import("@oai/artifact-tool");
  ({ FileBlob, SpreadsheetFile, Workbook } = module);
  if (!FileBlob || !SpreadsheetFile || !Workbook) fail("artifact-tool module is invalid");
}


function fail(message) {
  throw new Error(message);
}


function safeText(value) {
  const text = String(value ?? "");
  return /^[=+\-@]/.test(text) ? `'${text}` : text;
}


function excelDate(value) {
  if (!DATE_PATTERN.test(value)) fail("invalid date value");
  const [year, month, day] = value.split("-").map(Number);
  const parsed = new Date(Date.UTC(year, month - 1, day));
  if (
    parsed.getUTCFullYear() !== year
    || parsed.getUTCMonth() !== month - 1
    || parsed.getUTCDate() !== day
  ) {
    fail("invalid date value");
  }
  return parsed;
}


function dateKey(value) {
  if (value instanceof Date && !Number.isNaN(value.getTime())) {
    return value.toISOString().slice(0, 10);
  }
  if (typeof value === "number" && Number.isFinite(value)) {
    const epoch = Date.UTC(1899, 11, 30);
    return new Date(epoch + Math.floor(value) * 86400000).toISOString().slice(0, 10);
  }
  if (typeof value === "string" && DATE_PATTERN.test(value.slice(0, 10))) {
    return value.slice(0, 10);
  }
  return "";
}


function validatePayload(payload) {
  if (!payload || payload.schema_version !== "1.0") fail("invalid payload schema");
  if (!Array.isArray(payload.headers) || payload.headers.length !== EXPECTED_HEADERS.length) {
    fail("invalid headers");
  }
  if (!payload.headers.every((header, index) => header === EXPECTED_HEADERS[index])) {
    fail("invalid headers");
  }
  if (!Array.isArray(payload.periods) || payload.periods.length !== 3) {
    fail("invalid period count");
  }
  const names = new Set();
  for (const period of payload.periods) {
    if (
      typeof period.sheet_name !== "string"
      || period.sheet_name.length < 1
      || period.sheet_name.length > 31
      || names.has(period.sheet_name)
      || !Array.isArray(period.rows)
    ) {
      fail("invalid worksheet contract");
    }
    names.add(period.sheet_name);
    for (const row of period.rows) {
      if (
        !DATE_PATTERN.test(String(row.arrival ?? ""))
        || !DATE_PATTERN.test(String(row.departure ?? ""))
        || !Number.isInteger(row.nights)
        || row.nights < 0
        || typeof row.block_code !== "string"
        || typeof row.res_comment !== "string"
        || typeof row.booking_room !== "string"
        || typeof row.total_booking_price !== "string"
        || typeof row.duplicate_group !== "boolean"
        || typeof row.multi_price_review !== "boolean"
      ) {
        fail("invalid output row contract");
      }
    }
  }
}


function writeSheet(workbook, period, periodIndex) {
  const sheet = workbook.worksheets.add(period.sheet_name);
  const body = period.rows.map((row) => [
    excelDate(row.arrival),
    excelDate(row.departure),
    row.nights,
    safeText(row.block_code),
    safeText(row.res_comment),
    safeText(row.booking_room),
    safeText(row.total_booking_price),
  ]);
  const matrix = [EXPECTED_HEADERS, ...body];
  const lastRow = matrix.length;
  const used = sheet.getRange(`A1:G${lastRow}`);
  used.values = matrix;
  used.format.font = { name: "Arial", size: 10, color: BODY_FONT };
  used.format.verticalAlignment = "center";

  sheet.getRange("A1").format.columnWidth = 13;
  sheet.getRange("B1").format.columnWidth = 13;
  sheet.getRange("C1").format.columnWidth = 9;
  sheet.getRange("D1").format.columnWidth = 20;
  sheet.getRange("E1").format.columnWidth = 25;
  sheet.getRange("F1").format.columnWidth = 34;
  sheet.getRange("G1").format.columnWidth = 48;

  const table = sheet.tables.add(
    `A1:G${lastRow}`,
    true,
    `CompanyReportPeriod${periodIndex + 1}`,
  );
  table.style = "TableStyleLight1";
  table.showHeaders = true;
  table.showTotals = false;
  table.showBandedColumns = false;
  table.showFilterButton = true;

  const header = sheet.getRange("A1:G1");
  header.format.fill = HEADER_FILL;
  header.format.font = { name: "Arial", size: 10, bold: true, color: "#000000" };
  header.format.horizontalAlignment = "center";
  header.format.verticalAlignment = "center";
  header.format.rowHeight = 24;
  header.format.borders = {
    bottom: { style: "thin", color: "#7F7F7F" },
  };

  sheet.freezePanes.freezeRows(1);
  if (body.length > 0) {
    const data = sheet.getRange(`A2:G${lastRow}`);
    data.format.rowHeight = 30;
    sheet.getRange(`A2:B${lastRow}`).setNumberFormat("yyyy-mm-dd");
    sheet.getRange(`A2:D${lastRow}`).format.horizontalAlignment = "center";
    sheet.getRange(`E2:G${lastRow}`).format.horizontalAlignment = "left";
    sheet.getRange(`D2:G${lastRow}`).format.wrapText = true;

    period.rows.forEach((row, rowIndex) => {
      const excelRow = rowIndex + 2;
      if (row.duplicate_group) {
        sheet.getRange(`A${excelRow}:G${excelRow}`).format.fill = DUPLICATE_FILL;
        sheet.getRange(`E${excelRow}`).format.font = {
          name: "Arial",
          size: 10,
          bold: true,
          color: DUPLICATE_FONT,
        };
      }
      if (row.multi_price_review) {
        sheet.getRange(`G${excelRow}`).format.fill = REVIEW_FILL;
        sheet.getRange(`G${excelRow}`).format.font = {
          name: "Arial",
          size: 10,
          bold: true,
          color: REVIEW_FONT,
        };
      }
    });
    data.format.autofitRows();
  }
  sheet.showGridLines = true;
  return sheet;
}


async function validateWorkbook(workbook, payload, stage) {
  const names = workbook.worksheets.items.map((sheet) => sheet.name);
  const expectedNames = payload.periods.map((period) => period.sheet_name);
  if (JSON.stringify(names) !== JSON.stringify(expectedNames)) {
    fail(`${stage} worksheet names do not match`);
  }

  let formulaCount = 0;
  for (let index = 0; index < payload.periods.length; index += 1) {
    const period = payload.periods[index];
    const sheet = workbook.worksheets.getItem(period.sheet_name);
    const expectedRows = period.rows.length + 1;
    const used = sheet.getUsedRange();
    const values = used?.values ?? [];
    if (values.length !== expectedRows || (values[0]?.length ?? 0) !== 7) {
      fail(`${stage} worksheet dimensions do not match`);
    }
    if (!EXPECTED_HEADERS.every((header, column) => values[0][column] === header)) {
      fail(`${stage} worksheet headers do not match`);
    }
    period.rows.forEach((row, rowIndex) => {
      const actual = values[rowIndex + 1] ?? [];
      const expected = [
        row.arrival,
        row.departure,
        row.nights,
        safeText(row.block_code),
        safeText(row.res_comment),
        safeText(row.booking_room),
        safeText(row.total_booking_price),
      ];
      if (
        dateKey(actual[0]) !== expected[0]
        || dateKey(actual[1]) !== expected[1]
        || Number(actual[2]) !== expected[2]
        || String(actual[3] ?? "") !== expected[3]
        || String(actual[4] ?? "") !== expected[4]
        || String(actual[5] ?? "") !== expected[5]
        || String(actual[6] ?? "") !== expected[6]
      ) {
        fail(`${stage} worksheet values do not match`);
      }
    });
    if (sheet.tables.items.length !== 1 || !sheet.tables.items[0].showFilterButton) {
      fail(`${stage} worksheet filter is missing`);
    }
    const formulaInspection = await workbook.inspect({
      kind: "formula",
      sheetId: period.sheet_name,
      range: `A1:G${expectedRows}`,
      maxChars: 4000,
      options: { maxResults: 100 },
    });
    const text = String(formulaInspection.ndjson ?? "");
    formulaCount += text
      .split("\n")
      .filter((line) => line.includes('"kind":"formula"')).length;
    if (/#REF!|#DIV\/0!|#VALUE!|#NAME\?|#N\/A/.test(text)) {
      fail(`${stage} workbook contains a formula error`);
    }
  }
  if (formulaCount !== 0) fail(`${stage} workbook must contain no formulas`);
  return formulaCount;
}


async function main() {
  const [inputPath, outputPath, previewDir, summaryPath] = process.argv.slice(2);
  if (!inputPath || !outputPath || !previewDir || !summaryPath) {
    fail("usage: build_workbook.mjs INPUT_JSON OUTPUT_XLSX PREVIEW_DIR SUMMARY_JSON");
  }

  await loadArtifactTool();

  const payload = JSON.parse(await fs.readFile(inputPath, "utf8"));
  validatePayload(payload);
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
  await fs.mkdir(previewDir, { recursive: true });
  await fs.mkdir(path.dirname(summaryPath), { recursive: true });

  const workbook = Workbook.create();
  payload.periods.forEach((period, index) => writeSheet(workbook, period, index));
  await validateWorkbook(workbook, payload, "pre-export");

  const xlsx = await SpreadsheetFile.exportXlsx(workbook);
  await xlsx.save(outputPath);
  await fs.chmod(outputPath, 0o600);

  const reopened = await SpreadsheetFile.importXlsx(await FileBlob.load(outputPath));
  const formulaCount = await validateWorkbook(reopened, payload, "post-export");
  const previews = [];
  for (let index = 0; index < payload.periods.length; index += 1) {
    const period = payload.periods[index];
    const preview = await reopened.render({
      sheetName: period.sheet_name,
      autoCrop: "all",
      scale: 1.5,
      format: "png",
    });
    const previewPath = path.join(previewDir, `sheet-${index + 1}.png`);
    await fs.writeFile(previewPath, new Uint8Array(await preview.arrayBuffer()), {
      mode: 0o600,
    });
    previews.push(previewPath);
  }

  const stat = await fs.stat(outputPath);
  const semanticSha256 = crypto
    .createHash("sha256")
    .update(JSON.stringify({ headers: payload.headers, periods: payload.periods }), "utf8")
    .digest("hex");
  const summary = {
    status: "success",
    schema_version: payload.schema_version,
    company: payload.company,
    filename: payload.filename,
    sheet_names: payload.periods.map((period) => period.sheet_name),
    row_counts: payload.periods.map((period) => period.rows.length),
    formula_count: formulaCount,
    semantic_sha256: semanticSha256,
    preview_count: previews.length,
    byte_size: stat.size,
  };
  await fs.writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, {
    encoding: "utf8",
    mode: 0o600,
  });
  await fs.rm(`${outputPath}.inspect.ndjson`, { force: true });
  process.stdout.write(`${JSON.stringify(summary)}\n`);
}


try {
  await main();
} catch (_error) {
  const outputPath = process.argv[3];
  if (outputPath) {
    await fs.rm(`${outputPath}.inspect.ndjson`, { force: true }).catch(() => undefined);
  }
  process.stderr.write(
    `${JSON.stringify({
      status: "failed",
      code: "COMPANY_REPORT_OUTPUT_VALIDATION_FAILED",
    })}\n`,
  );
  process.exitCode = 4;
}
