const {
  getSyncDatasetKeys,
  listInvimaDatasets,
  syncInvimaDatasets
} = require('./service');

let activeJob = null;
let lastJob = null;
let sequence = 0;

const cloneJob = (job) => (job ? JSON.parse(JSON.stringify(job)) : null);

const nowIso = () => new Date().toISOString();

const datasetMetadata = () => {
  const map = new Map();
  for (const item of listInvimaDatasets()) {
    map.set(item.key, item);
  }
  return map;
};

const recalculateTotals = (job) => {
  job.completed = job.datasets.filter((item) => item.status === 'completed').length;
  job.failed = job.datasets.filter((item) => item.status === 'failed').length;
  job.fetched = job.datasets.reduce((total, item) => total + Number(item.fetched || 0), 0);
  job.persisted = job.datasets.reduce((total, item) => total + Number(item.persisted || 0), 0);
  job.pages = job.datasets.reduce((total, item) => total + Number(item.pages || 0), 0);
  job.updatedAt = nowIso();
};

const findDatasetProgress = (job, datasetKey) =>
  job.datasets.find((item) => item.key === datasetKey || item.datasetKey === datasetKey);

const applyProgress = (job, event) => {
  if (!job || !event) return;

  if (event.type === 'sync_started') {
    job.requested = Array.isArray(event.keys) ? event.keys.length : job.requested;
    job.concurrency = event.concurrency || job.concurrency;
    recalculateTotals(job);
    return;
  }

  const dataset = findDatasetProgress(job, event.datasetKey);
  if (!dataset) {
    recalculateTotals(job);
    return;
  }

  if (event.type === 'dataset_started') {
    dataset.status = 'running';
    dataset.startedAt = dataset.startedAt || nowIso();
  }

  if (event.type === 'page_persisted') {
    dataset.status = 'running';
    dataset.fetched = Number(event.fetched || dataset.fetched || 0);
    dataset.persisted = Number(event.persisted || dataset.persisted || 0);
    dataset.pages = Number(event.pages || dataset.pages || 0);
    dataset.pageSizeUsed = event.pageSizeUsed || dataset.pageSizeUsed || null;
    dataset.warnings = Array.isArray(event.warnings) ? event.warnings : dataset.warnings;
  }

  if (event.type === 'dataset_completed') {
    dataset.status = 'completed';
    dataset.datasetId = event.datasetId || dataset.datasetId;
    dataset.fetched = Number(event.fetched || dataset.fetched || 0);
    dataset.persisted = Number(event.persisted || dataset.persisted || 0);
    dataset.pages = Number(event.pages || dataset.pages || 0);
    dataset.pageSizeUsed = event.pageSizeUsed || dataset.pageSizeUsed || null;
    dataset.orderUsed = event.orderUsed || dataset.orderUsed || null;
    dataset.warnings = Array.isArray(event.warnings) ? event.warnings : dataset.warnings;
    dataset.finishedAt = nowIso();
  }

  if (event.type === 'dataset_failed') {
    dataset.status = 'failed';
    dataset.error = event.error || 'sync_failed';
    dataset.finishedAt = nowIso();
  }

  dataset.updatedAt = nowIso();
  recalculateTotals(job);
};

const finishJob = (job, status, resultOrError) => {
  if (!job) return;

  if (resultOrError?.datasets) {
    for (const result of resultOrError.datasets) {
      applyProgress(job, { type: 'dataset_completed', ...result });
    }
  }

  if (resultOrError?.failed) {
    for (const failure of resultOrError.failed) {
      applyProgress(job, { type: 'dataset_failed', ...failure });
    }
  }

  job.status = status;
  job.summary = resultOrError?.summary || job.summary || null;
  job.error = resultOrError instanceof Error ? String(resultOrError.message || resultOrError) : null;
  job.finishedAt = nowIso();
  recalculateTotals(job);
};

const createJob = (keys, options) => {
  const meta = datasetMetadata();
  const startedAt = nowIso();
  return {
    id: `${Date.now()}-${++sequence}`,
    status: 'running',
    requested: keys.length,
    completed: 0,
    failed: 0,
    fetched: 0,
    persisted: 0,
    pages: 0,
    concurrency: options.concurrency,
    pageSize: options.pageSize,
    datasets: keys.map((key) => {
      const dataset = meta.get(key) || {};
      return {
        key,
        datasetKey: key,
        datasetId: dataset.id || null,
        name: dataset.name || key,
        status: 'pending',
        fetched: 0,
        persisted: 0,
        pages: 0,
        warnings: [],
        error: null,
        startedAt: null,
        updatedAt: startedAt,
        finishedAt: null
      };
    }),
    summary: null,
    error: null,
    startedAt,
    updatedAt: startedAt,
    finishedAt: null
  };
};

const startInvimaSyncJob = (pool, options = {}) => {
  if (activeJob?.status === 'running') {
    return { alreadyRunning: true, job: cloneJob(activeJob) };
  }

  const keys = getSyncDatasetKeys(options.datasets);
  if (!keys.length) {
    const error = new Error('No hay datasets validos para sincronizar.');
    error.status = 400;
    throw error;
  }

  const job = createJob(keys, options);
  activeJob = job;
  lastJob = job;

  syncInvimaDatasets(pool, {
    ...options,
    datasets: keys,
    onProgress: (event) => applyProgress(job, event)
  })
    .then((result) => {
      const hasSuccessfulDatasets = Number(result?.summary?.datasets || 0) > 0;
      const status = hasSuccessfulDatasets || !result?.failed?.length ? 'completed' : 'failed';
      finishJob(job, status, result);
    })
    .catch((error) => {
      finishJob(job, 'failed', error);
    })
    .finally(() => {
      if (activeJob?.id === job.id) {
        activeJob = null;
      }
    });

  return { alreadyRunning: false, job: cloneJob(job) };
};

const getInvimaSyncJob = () => cloneJob(activeJob || lastJob);

module.exports = {
  getInvimaSyncJob,
  startInvimaSyncJob
};
