import {
  ClarizenApiError,
  entityObjectPath,
  type Clarizen,
  type EntityId,
  type JsonValue,
} from "@arco/clarizen-lib";
import type { EntityRecord } from "../query/fetch-all.js";
import { ProgressReporter } from "./progress.js";

const BATCH_SIZE = 100;

export interface UpdateError {
  id: EntityId;
  displayName?: string;
  message: string;
  errorCode?: string;
}

export interface BulkUpdateResult {
  updated: number;
  errors: UpdateError[];
}

interface BulkResponse {
  responses?: Array<{
    statusCode?: number;
    body?: unknown;
  }>;
}

function shortId(id: EntityId): string {
  const str = String(id);
  return str.includes("/") ? str.split("/").pop()! : str;
}

function buildUpdateBody(fieldName: string, value: JsonValue): string {
  return JSON.stringify({ [fieldName]: value });
}

function extractErrorMessage(body: unknown): { message: string; errorCode?: string } {
  if (body && typeof body === "object") {
    const obj = body as Record<string, unknown>;
    const message =
      typeof obj.message === "string" ? obj.message : JSON.stringify(body);
    const errorCode =
      typeof obj.errorCode === "string" ? obj.errorCode : undefined;
    return { message, errorCode };
  }
  return { message: String(body) };
}

async function updateSingle(
  clarizen: Clarizen,
  typeName: string,
  id: EntityId,
  fieldName: string,
  value: JsonValue,
): Promise<void> {
  await clarizen.objects.update(typeName, id, { [fieldName]: value });
}

async function updateBatchBulk(
  clarizen: Clarizen,
  typeName: string,
  batch: EntityRecord[],
  fieldName: string,
  value: JsonValue,
): Promise<Array<{ record: EntityRecord; error?: UpdateError }>> {
  const requests = batch.map((record) => ({
    url: entityObjectPath(typeName, shortId(record.id)).replace(
      /^\/V2\.0\/services/,
      "",
    ),
    method: "POST" as const,
    body: buildUpdateBody(fieldName, value),
  }));

  try {
    const result = (await clarizen.bulk.execute({
      requests,
    })) as BulkResponse;

    const responses = result.responses ?? [];
    return batch.map((record, index) => {
      const response = responses[index];
      if (!response || response.statusCode === 200) {
        return { record };
      }
      const { message, errorCode } = extractErrorMessage(response.body);
      return {
        record,
        error: {
          id: record.id,
          displayName: record.displayName,
          message,
          errorCode,
        },
      };
    });
  } catch (err) {
    const message =
      err instanceof ClarizenApiError
        ? err.message
        : err instanceof Error
          ? err.message
          : "Falha no lote bulk";
    return batch.map((record) => ({
      record,
      error: {
        id: record.id,
        displayName: record.displayName,
        message,
        errorCode:
          err instanceof ClarizenApiError ? String(err.errorCode) : undefined,
      },
    }));
  }
}

async function retryIndividually(
  clarizen: Clarizen,
  typeName: string,
  record: EntityRecord,
  fieldName: string,
  value: JsonValue,
): Promise<UpdateError | undefined> {
  try {
    await updateSingle(clarizen, typeName, record.id, fieldName, value);
    return undefined;
  } catch (err) {
    if (err instanceof ClarizenApiError) {
      return {
        id: record.id,
        displayName: record.displayName,
        message: err.message,
        errorCode: String(err.errorCode),
      };
    }
    return {
      id: record.id,
      displayName: record.displayName,
      message: err instanceof Error ? err.message : "Erro desconhecido",
    };
  }
}

export async function bulkUpdateField(
  clarizen: Clarizen,
  typeName: string,
  records: EntityRecord[],
  fieldName: string,
  value: JsonValue,
): Promise<BulkUpdateResult> {
  const progress = new ProgressReporter(records.length);
  let updated = 0;
  const errors: UpdateError[] = [];

  for (let i = 0; i < records.length; i += BATCH_SIZE) {
    const batch = records.slice(i, i + BATCH_SIZE);
    const results = await updateBatchBulk(
      clarizen,
      typeName,
      batch,
      fieldName,
      value,
    );

    for (const { record, error } of results) {
      if (!error) {
        updated++;
        progress.update(updated + errors.length);
        continue;
      }

      const retryError = await retryIndividually(
        clarizen,
        typeName,
        record,
        fieldName,
        value,
      );
      if (retryError) {
        errors.push(retryError);
      } else {
        updated++;
      }
      progress.update(updated + errors.length);
    }
  }

  progress.finish();
  return { updated, errors };
}
