import * as fs from 'fs';
import * as path from 'path';

import {
  emit,
  emitter,
  readyEmit,
  readConfig,
  cloneObject,
  readRemoteConfig,
} from './utils/utils';
import addCamerasInConfig from './utils/addCamerasInConfig';
import stylesConfig from './utils/styleNames';
import createConfigPage from './utils/recursions/createConfigPage/createConfigPage';
import syncConfig from './utils/recursions/syncConfig/syncConfig';
import parseConfig from './utils/recursions/parseConfig/parseConfig';
import checkConfig from './utils/recursions/checkConfig/checkConfig';

const config = {} as Config;
let isConfigOpened = false;
let isConfigChanged = false;
let isInitialized = false;
const paths = {
  local: '',
  device: '',
  default: '',
  remoteUrl: '',
};
const newFields: string[] = []; // Если включена загрузка удалённого конфига, то при синхронизации конфигов здесь будут храниться новые поля

// инициализация модуля
const init = ({
  appName,
  localConfigPath,
  deviceConfigPath,
  defaultConfigPath,
}: InitProps) => {
  if (isInitialized) {
    emit('error', 'конфиг уже инициализирован');
    return;
  }

  // создаём абсолютные пути и читаем конфиги
  paths.device = path.resolve(
    path.join(deviceConfigPath, `${appName}-device-config.json`),
  );
  paths.local = path.resolve(path.join(localConfigPath, `${appName}-app-config.json`));
  paths.default = path.resolve(
    path.join(defaultConfigPath, `${appName}-config-default.json`),
  );

  readDefaultConfig();

  readLocalConfig();

  readDeviceConfig();

  if (!config.default && !config.local) {
    throw new Error(
      'Невозможно построить конфиг, так как отсутстувуют локальный и дефолтный конфиги',
    );
  }

  if (!config.local) {
    isConfigChanged = true;
  }

  // читаем удалённый конфиг
  readRemoteConfig(config, paths.remoteUrl).then(async (remoteConfig) => {
    config.remote = remoteConfig;

    // объединяем все конфиги, которые есть в наличии
    config.united = syncConfig(
      cloneObject(config.remote || {}),
      cloneObject(config.local || {}),
      cloneObject(config.default || {}),
      newFields,
    );

    if (newFields.length > 0) {
      isConfigChanged = true;
    }

    checkFieldForCameras();

    // добавляем камеры
    const cameras = await addCamerasInConfig(config);

    if (
      JSON.stringify(config.united.devices.value.camera.value) !== JSON.stringify(cameras)
    ) {
      config.united.devices.value.camera.value = cameras;
      isConfigChanged = true;
    }

    // сохраняем конфиги, если есть изменения
    if (checkConfig(config.united) || isConfigChanged) {
      fs.writeFileSync(paths.local, JSON.stringify(config.united, null, '\t'));
      isConfigChanged = false;
    }

    if (config.default && checkConfig(config.default)) {
      fs.writeFileSync(paths.default, JSON.stringify(config.default, null, '\t'));
    }

    config.parsed = parseConfig(config.united);

    readyEmit(config.parsed);

    isInitialized = true;
  });
};

const checkFieldForCameras = () => {
  if (!config.united?.devices?.value?.camera?.value) {
    config.united.devices = config.united.devices || {
      name: 'Периферийные устройства',
      key: 'devices',
    };
    config.united.devices.value = config.united.devices.value || {};
    config.united.devices.value.camera = config.united.devices.value.camera || {
      name: 'Камера',
      key: 'camera',
    };
    isConfigChanged = true;
  }
}

const readDefaultConfig = () => {
  config.default = readConfig(
    paths.default,
    () => emit('info', 'конфиг по умолчанию конфиг прочитан'),
    () => emit('error', 'конфиг по умолчанию не найден'),
    () => emit('error', 'не удалось прочитать конфиг по умолчанию'),
    () => emit('warn', 'файл с дефолтным конфигом пуст'),
  );
};

const readLocalConfig = () => {
  config.local = readConfig(
    paths.local,
    () => emit('info', 'локальный конфиг прочитан'),
    () => emit('warn', 'локальный конфиг не найден'),
    () => emit('error', 'не удалось прочитать локальный конфиг'),
    () =>
      emit('warn', 'файл с локальным конфигом пуст. Ему будет присвоен пустой объект'),
  );
};

const readDeviceConfig = () => {
  const deviceConfig = readConfig(
    paths.device,
    () => emit('info', 'конфиг устройства прочитан'),
    () => emit('error', 'конфиг устройства не найден'),
    () => emit('error', 'не удалось прочитать конфиг устройства'),
    () => emit('warn', 'файл с дефолтным устройства конфигом пуст'),
  );

  if (deviceConfig) {
    paths.remoteUrl = deviceConfig.remote_href;
    config.device = deviceConfig.device;
  }
};

const toggleDevMode = (e: KeyboardEvent) => {
  if (e.ctrlKey && e.shiftKey && e.code === 'KeyX' && isConfigOpened) {
    // удаляем форму создания нового поля, если она есть
    if (config.HTML.classList.contains(stylesConfig.devMode)) {
      config.HTML.querySelector(`.${stylesConfig.form}`)?.remove();
    }
    config.HTML.classList.toggle(stylesConfig.devMode);
  }
};

const open = async (root?: HTMLElement) => {
  if (!isConfigOpened) {
    if (root) {
      config.root = root;
    }

    if (config.root) {
      // перепроверяем камеры, обновляем экспортный конфиг и строим HTML страницу
      checkFieldForCameras();
      config.united.devices.value.camera.value = await addCamerasInConfig(config);
      config.parsed = parseConfig(config.united);
      config.HTML = await createConfigPage({
        config: config,
        newFields,
        isConfigChanged,
        pathToSaveLocalConfig: paths.local,
        pathToSaveDefaultConfig: paths.default,
      });

      try {
        config.root.appendChild(config.HTML);

        emit('open', 'конфиг открыт');

        isConfigOpened = true;

        document.addEventListener('keydown', toggleDevMode);
      } catch (err) {
        console.error(err);
        emit('error', 'произошла ошибка при попытке открыть страницу конфига');
      }
    } else {
      emit('error', 'родительский элемент не задан');
    }
  }
};

const close = () => {
  if (isConfigOpened) {
    try {
      config.root.removeChild(config.HTML);

      emit('close', 'конфиг закрыт');

      isConfigOpened = false;

      document.removeEventListener('keydown', toggleDevMode);
    } catch (err) {
      console.error(err);
      emit('error', 'произошла ошибка при попытке закрыть конфиг');
    }
  }
};

const isOpened = (): boolean => isConfigOpened;

const getConfig = (): DynamicObject => config.parsed;

const getDeviceConfig = (): DynamicObject | null => config.device ? cloneObject(config.device) : null;

const setRoot = (root: HTMLElement) => {
  config.root = root;
};

const on = (
  event: ConfigEvents,
  action: (message: string, config?: DynamicObject) => void,
) => {
  emitter.on(event, (message: string, config?: DynamicObject) => action(message, config));
};

export { init, open, close, isOpened, getConfig, on, setRoot, getDeviceConfig };
