import "./load-env.js";
import { writeFileSync } from "node:fs";
import { resolve } from "node:path";
import {
  cancel,
  confirm,
  intro,
  isCancel,
  outro,
  select,
  spinner,
  text,
} from "@clack/prompts";
import {
  checkClarizenAuth,
  createClarizenClient,
} from "./clarizen/client.js";
import { formatFieldHint } from "./metadata/field-hints.js";
import { isPickListField } from "./metadata/picklist.js";
import { getEntityMetadata, pickEntityType, pickField } from "./metadata/picker.js";
import {
  promptPickListValue,
  type FieldValueSelection,
} from "./parse/prompt-picklist.js";
import {
  formatValueForDisplay,
  parseFieldValue,
  ValueParseError,
} from "./parse/value.js";
import { formatFilterSummary } from "./query/filter.js";
import { fetchAllEntities } from "./query/fetch-all.js";
import { promptEntityFilter } from "./query/prompt-filter.js";
import { buildRunLog, writeRunLog } from "./log/run-log.js";
import { bulkUpdateField } from "./update/bulk-field.js";
import type { Clarizen, FieldDescription, JsonValue } from "@arco/clarizen-lib";

function exitCancel(): never {
  cancel("Operação cancelada.");
  process.exit(0);
}

async function promptFieldValue(
  clarizen: Clarizen,
  field: FieldDescription,
): Promise<FieldValueSelection | symbol> {
  if (isPickListField(field)) {
    return promptPickListValue(clarizen, field);
  }

  console.log("\n" + formatFieldHint(field) + "\n");

  if (field.nullable) {
    const action = await select({
      message: "O que deseja fazer com este campo?",
      options: [
        { value: "set", label: "Definir um valor" },
        { value: "clear", label: "Limpar campo (null)" },
      ],
    });
    if (isCancel(action)) return action;
    if (action === "clear") return { value: null, displayLabel: "(vazio / null)" };
  }

  while (true) {
    const raw = await text({
      message: "Informe o novo valor",
      validate: (value) => {
        if (!field.nullable && !value?.trim()) {
          return "Valor obrigatório.";
        }
        return undefined;
      },
    });
    if (isCancel(raw)) return raw;

    try {
      const value = parseFieldValue(raw, field);
      return { value };
    } catch (err) {
      if (err instanceof ValueParseError) {
        console.error(`\n  Erro: ${err.message}\n`);
        continue;
      }
      throw err;
    }
  }
}

async function main(): Promise<void> {
  intro("Atualização em massa de campo — Clarizen");

  const clarizen = createClarizenClient();

  const authSpinner = spinner();
  authSpinner.start("Autenticando...");
  const auth = await checkClarizenAuth(clarizen);
  if (!auth.ok) {
    authSpinner.stop(`Falha na autenticação: ${auth.message}`);
    process.exit(1);
  }
  authSpinner.stop(`Autenticado (usuário: ${auth.userId})`);

  const entityChoice = await pickEntityType(clarizen);
  if (isCancel(entityChoice)) exitCancel();

  const entityMeta = await getEntityMetadata(clarizen, entityChoice.typeName);

  const entityFilter = await promptEntityFilter(
    clarizen,
    entityChoice.typeName,
    entityMeta,
  );
  if (isCancel(entityFilter)) exitCancel();

  const fieldChoice = await pickField(clarizen, entityChoice.typeName);
  if (isCancel(fieldChoice)) exitCancel();

  const { field } = fieldChoice;
  const fieldName = field.name!;

  const valueSelection = await promptFieldValue(clarizen, field);
  if (isCancel(valueSelection)) exitCancel();

  const parsedValue = valueSelection.value;
  const valueDisplayLabel = valueSelection.displayLabel;

  const fetchSpinner = spinner();
  fetchSpinner.start("Buscando objetos...");
  const records = await fetchAllEntities(
    clarizen,
    entityChoice.typeName,
    entityMeta,
    entityFilter,
    (count) => {
      fetchSpinner.message(`Buscando objetos... (${count} encontrados)`);
    },
  );
  fetchSpinner.stop(`${records.length} objeto(s) encontrado(s).`);

  if (records.length === 0) {
    outro("Nenhum objeto encontrado com esse filtro.");
    return;
  }

  const entityLabel = entityChoice.label
    ? `${entityChoice.typeName} (${entityChoice.label})`
    : entityChoice.typeName;

  console.log(`
Resumo da operação:
  Tipo:     ${entityLabel}
  Filtro:   ${formatFilterSummary(entityFilter)}
  Campo:    ${field.label ?? fieldName} (${fieldName}) — ${field.type ?? "?"}
  Valor:    ${formatValueForDisplay(parsedValue, valueDisplayLabel)}
  Objetos:  ${records.length.toLocaleString("pt-BR")} registro(s)
`);

  const proceed = await confirm({
    message: `Confirma a atualização de ${records.length.toLocaleString("pt-BR")} objeto(s)?`,
    initialValue: false,
  });
  if (isCancel(proceed) || !proceed) exitCancel();

  console.log("\nAtualizando...\n");

  const startedAt = Date.now();

  const result = await bulkUpdateField(
    clarizen,
    entityChoice.typeName,
    records,
    fieldName,
    parsedValue,
  );

  const logPath = writeRunLog(
    buildRunLog({
      startedAt,
      userId: auth.userId,
      entityChoice,
      entityFilter,
      field,
      fieldName,
      parsedValue,
      valueDisplayLabel,
      records,
      result,
    }),
  );

  if (result.errors.length > 0) {
    const errorFile = resolve(
      process.cwd(),
      `errors-${new Date().toISOString().replace(/[:.]/g, "-")}.json`,
    );
    writeFileSync(errorFile, JSON.stringify(result.errors, null, 2), "utf-8");
    console.log(`\n${result.errors.length} erro(s) — detalhes em: ${errorFile}`);
  }

  console.log(`\nLog da execução: ${logPath}`);

  outro(
    `Concluído: ${result.updated.toLocaleString("pt-BR")} atualizado(s), ${result.errors.length.toLocaleString("pt-BR")} erro(s).`,
  );
}

main().catch((err) => {
  console.error(err instanceof Error ? err.message : err);
  process.exit(1);
});
