import { ModuleParams } from "@erp/core/api";
import { ENV } from "../../config";
import { logger } from "../logger";

/**
  Tipos mínimos para evitar acoplarse a una versión concreta de Sequelize
*/
type SequelizeLike = {
  sync: (options?: any) => Promise<void>;
  models: Record<string, unknown>;
};

type ModelStatic = {
  name: string;
  // Algunas implementaciones añaden associate(sequelize) para definir relaciones
  associate?: (sequelize: SequelizeLike) => void;
};

export type ModelInitializer = (sequelize: SequelizeLike) => ModelStatic;

/**
  Estructuras internas (no exportadas) con trazabilidad de módulo.
*/
type InitializerEntry = { init: ModelInitializer; moduleName: string };

const allModelInitializers: InitializerEntry[] = [];
const registeredModels: Map<string, { model: ModelStatic; moduleName: string }> = new Map();

let modelsInitialized = false;

/**
  🔹 Registra inicializadores de modelos de un módulo
    models: lista de funciones que definen modelos (sequelize) => ModelStatic
    ctx.moduleName: para trazabilidad y detección de colisiones
*/
export const registerModels = (
  models: ModelInitializer[],
  _params?: ModuleParams,
  ctx?: { moduleName: string }
) => {
  const moduleName = ctx?.moduleName ?? "unknown";
  if (!Array.isArray(models) || models.length === 0) {
    logger.warn(`No models provided by module "${moduleName}"`, { label: "registerModels" });
    return;
  }
  for (const init of models) {
    // Guardamos con el módulo que los aporta (para logs/errores)
    allModelInitializers.push({ init, moduleName });
  }
  logger.info(`📦 ${models.length} model initializer(s) enqueued from "${moduleName}"`, {
    label: "registerModels",
  });
};

/**
  🔹 Inicializa todos los modelos registrados y configura asociaciones.
  Detecta colisiones por nombre de modelo entre módulos.
  Controla el modo de sincronización por ENV.
*/
export const initModels = async (params: ModuleParams) => {
  if (modelsInitialized) {
    logger.warn("Models already initialized. Skipping initModels()", { label: "initModels" });
    return;
  }

  logger.info("Init models...", { label: "initModels" });

  const { database } = params as { database: SequelizeLike };
  if (!database) {
    const error = new Error("❌ Database not found.");
    logger.error(error.message, { label: "initModels" });
    throw error;
  }

  // 1) Definir modelos (y detectar colisiones)
  for (const { init, moduleName } of allModelInitializers) {
    try {
      const model = init(database);
      if (!model || typeof model.name !== "string" || !model.name) {
        throw new Error(`Invalid model initializer: missing or empty "name"`);
      }

      if (registeredModels.has(model.name)) {
        const existing = registeredModels.get(model.name)!;
        throw new Error(
          `Model name collision: "${model.name}" from module "${moduleName}" conflicts with existing model from "${existing.moduleName}"`
        );
      }

      registeredModels.set(model.name, { model, moduleName });
      logger.info(`🔸 Model "${model.name}" registered (sequelize)`, {
        label: "registerModel",
        module: moduleName,
      });
    } catch (err: any) {
      // Agregamos contexto del módulo que aportó el initializer
      logger.error(`⛔️ Failed to define model (module="${moduleName}") : ${err?.message ?? err}`, {
        label: "initModels",
        stack: err?.stack,
      });
      throw err;
    }
  }

  // 2) Configurar asociaciones
  for (const { model, moduleName } of registeredModels.values()) {
    if (typeof model.associate === "function") {
      try {
        model.associate(database);
      } catch (err: any) {
        logger.error(
          `⛔️ Failed to associate model "${model.name}" (module="${moduleName}") : ${err?.message ?? err}`,
          { label: "initModels", stack: err?.stack }
        );
        throw err;
      }
    }
  }

  // 3) Sincronizar base de datos (según modo)
  const nodeEnv = ENV.NODE_ENV ?? process.env.NODE_ENV ?? "development";
  const syncModeEnv = (ENV as any).DB_SYNC_MODE ?? process.env.DB_SYNC_MODE; // "none" | "alter" | "force"
  const defaultMode = nodeEnv === "production" ? "none" : "alter";
  const mode = (syncModeEnv ?? defaultMode) as "none" | "alter" | "force";

  try {
    if (mode === "none") {
      logger.info("✔️ Database sync skipped (mode=none)", { label: "initModels" });
    } else if (mode === "alter") {
      await database.sync({ force: false, alter: true });
      logger.info("✔️ Database synchronized successfully (mode=alter).", { label: "initModels" });
    } else if (mode === "force") {
      await database.sync({ force: true, alter: false });
      logger.warn("⚠️ Database synchronized with FORCE (mode=force).", { label: "initModels" });
    } else {
      logger.warn(`⚠️ Unknown DB sync mode "${String(mode)}" → skipping`, { label: "initModels" });
    }
  } catch (err) {
    const error = err as Error;
    logger.error("❌ Error synchronizing database:", { error, label: "initModels" });
    throw error;
  } finally {
    modelsInitialized = true;
  }
};

/**
  🔹 Utilidades opcionales (DX/tests)
*/
export const getRegisteredModels = () =>
  Array.from(registeredModels.entries()).map(([name, { moduleName }]) => ({
    name,
    moduleName,
  }));

export const resetModelsRegistry = () => {
  allModelInitializers.length = 0;
  registeredModels.clear();
  modelsInitialized = false;
  logger.info("Model registry has been reset.", { label: "initModels" });
};
