import { parse } from 'jsonc-parser';
import { readFileSync } from 'fs';
import { parseArgs } from 'util';
import { parse as csvParse } from 'csv/sync';
import { DateTime } from 'luxon';
import { createInterface } from 'readline/promises';

const helpMsg = `
Синхронизация задач из Redmine с Redmine Issue Event Emitter

* --config=sync-issues.jsonc - конфигурационный файл для постановки задачи на выполнение синхронизации
* --test - тестовый прогон без запуска задачи на синхронизацию
* --yes - автоматически отвечать 'y'

Пример конфигурационного файла:

{
  "redmineUrl": "http://redmine.my-company.local",
  "redmineToken": "... ... ...",
  "eventEmitterUrl": "http://localhost:3000"
  "forceUpdate": true, // true - принудительное обновление,
                       // false - сравнение даты обновления в redmine и в event-emitter-е
                       // и обновление только просроченных задач
  "updatedAtFieldName": "Обновлено",
  "dateTimeFormat": "dd.MM.yyyy HH:mm",
  
  "csvLinks": [
    "http://redmine.my-company.local/projects/test-project/issues.csv?query_id=2011", // закрытые за сутки
    "http://redmine.my-company.local/projects/test-project/issues.csv?query_id=2012"  // обновленые за сутки
  ],
  "csvFiles": [
    "/tmp/file1.csv",
    "/tmp/file2.csv"
  ],
}
`;

var args = parseArgs({
  options: {
    config: {
      type: 'string',
    },
    test: {
      type: 'boolean',
    },
    yes: {
      type: 'boolean',
    },
  },
});

if (!args?.values?.config) {
  console.log(helpMsg);
  process.exit(0);
}

function readConfig(fileName) {
  const rawData = readFileSync(fileName, { encoding: 'utf8' });
  return parse(rawData);
}

var config = readConfig(args.values.config);

console.log(`Конфигурационный файл:\n${JSON.stringify(config, null, '  ')}\n`); // DEBUG

const UNLIMIT = 999999;

async function getIssuesByQuery(selector) {
  const resp = await fetch(`${config.eventEmitterUrl}/issues/find`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      selector: selector,
      limit: UNLIMIT,
    }),
  });
  return await resp.json();
}

async function getIssuesByList(issueIds) {
  return await getIssuesByQuery({
    id: {
      $in: issueIds,
    },
  });
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getCsvFromRedmine(url, token) {
  const resp = await fetch(url, {
    headers: {
      'X-Redmine-API-Key': token,
    },
  });
  const rawData = await resp.text();
  return csvParse(rawData, {
    delimiter: ';',
    quote: '"',
    columns: true,
    skip_empty_lines: true,
    cast: (value, context) => {
      if (context.column == '#') {
        return Number(value);
      }
      return value;
    },
  });
}

async function getCsvFromLocalFile(fileName) {
  const rawData = readFileSync(fileName, { encoding: 'utf8' });
  const data = csvParse(rawData, {
    delimiter: ';',
    quote: '"',
    columns: true,
    skip_empty_lines: true,
    cast: (value, context) => {
      if (context.column == '#') {
        return Number(value);
      }
      return value;
    },
  });
  return data;
}

async function loadAllCsv() {
  const res = {};
  const csvLinks = config?.csvLinks || [];
  const csvFiles = config?.csvFiles || [];
  const token = config?.redmineToken || '';
  function appendIssuesToRes(issues) {
    issues.forEach((issue) => {
      const issueId = Object.values(issue)[0];
      issue.id = Number(issueId);
      const updatedAt = DateTime.fromFormat(
        issue[config.updatedAtFieldName],
        config.dateTimeFormat,
      );
      if (!res[issueId]) {
        res[issueId] = issue;
      } else {
        const existsIssue = res[issueId];
        const existsUpdatedAt = DateTime.fromFormat(
          existsIssue[config.updatedAtFieldName],
          config.dateTimeFormat,
        );
        if (updatedAt.diff(existsUpdatedAt).as('seconds') > 0) {
          res[issueId] = issue;
        }
      }
    });
  }
  for (let i = 0; i < csvLinks.length; i++) {
    const link = csvLinks[i];
    const issues = await getCsvFromRedmine(link, token);
    appendIssuesToRes(issues);
  }
  for (let i = 0; i < csvFiles.length; i++) {
    const file = csvFiles[i];
    const issues = await getCsvFromLocalFile(file);
    appendIssuesToRes(issues);
  }
  return Object.values(res);
}

async function loadFromEventEmitter(issueIds) {
  return await getIssuesByList(issueIds);
}

async function getIssueIdsForUpdate(newIssues) {
  if (config.forceUpdate) {
    return newIssues.map((i) => i.id);
  }
  const existsIssue = await loadFromEventEmitter(newIssues.map((i) => i.id));
  const existsIssueMap = existsIssue.reduce((acc, issue) => {
    acc[issue.id] = issue;
    return acc;
  }, {});
  const newIds = newIssues
    .filter((newIssue) => {
      const existsIssue = existsIssueMap[newIssue.id];
      if (!existsIssue) return true;
      const newUpdatedAt = DateTime.fromFormat(
        newIssue[config.updatedAtFieldName],
        config.dateTimeFormat,
      );
      const existsUpdatedTimestamp = DateTime.fromMillis(
        existsIssue.timestamp__,
      );
      if (newUpdatedAt.diff(existsUpdatedTimestamp).as('seconds') > 0) {
        return true;
      }
      return false;
    })
    .map((i) => i.id);
  return newIds;
}

const issues = await loadAllCsv();
const idsForUpdate = await getIssueIdsForUpdate(issues);

console.log(`Из csv получено задач - ${issues.length} шт.`);
console.log(`Нужно обновить задач - ${idsForUpdate.length} шт.`);
console.log(`Задачи для обновления - ` + JSON.stringify(idsForUpdate));

async function continueQuestion() {
  if (args.values.yes) {
    return true;
  }
  const readline = createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  const answer = await readline.question(`\nПродолжить? (y/n)\n> `);
  const res = answer == 'y';
  readline.close();
  console.log(res ? `\nПродолжаем...` : '\nНу и мне тогда это не надо');
  return res;
}

if (!args.values.test) {
  const answer = await continueQuestion();
  if (answer) {
    console.log(`Отправка задачи на синхронизацию...`);
    await fetch(
      `${config.eventEmitterUrl}/redmine-event-emitter/append-issues`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(idsForUpdate),
      },
    );
    console.log(`Синхронизация запущена`);
  }
}
