import { ensureDir, pathExists, readJSON, writeFile } from 'fs-extra';
import { Main } from '../Main';
import { join } from 'path';
import { DefaultConfiguration } from './DefaultConfiguration';

const PREFIX = '[ConfigurationManager]';
export class ConfigurationManager {
	private _Main: Main;

	constructor(Main: Main) {
		this._Main = Main;
	}

	async load() {
		console.log(PREFIX, 'Loading');

		await ensureDir(join(this._Main.dataPath));

		let configPath = join(this._Main.dataPath, 'config.json');
		let configExists = await pathExists(configPath);
		if (!configExists) {
			await writeFile(
				configPath,
				JSON.stringify(DefaultConfiguration, null, 4),
			);
			this._Main.Config = DefaultConfiguration;
			console.log(PREFIX, 'Written default configuration');
		} else {
			var config: Config = await readJSON(configPath);

			this._Main.Config = await this.validateConfig(config);
			console.log(PREFIX, 'Loaded configuration');
		}
	}

	async validateConfig(config: Config): Promise<Config> {
		const normalizedConfig: Config = this.normalizeConfig(
			config,
			DefaultConfiguration,
		);
		const hasChanges =
			JSON.stringify(config) !== JSON.stringify(normalizedConfig);

		if (hasChanges) {
			this._Main.Config = normalizedConfig;

			const configPath = join(this._Main.dataPath, 'config.json');
			await writeFile(
				configPath,
				JSON.stringify(normalizedConfig, null, 4),
			);
			console.log(PREFIX, 'Configuration updated and saved');
		}

		return normalizedConfig;
	}

	private normalizeConfig(current: any, template: any): any {
		if (template === null || template === undefined) {
			return template;
		}

		if (typeof template !== 'object' || Array.isArray(template)) {
			if (
				current !== null &&
				current !== undefined &&
				typeof current === typeof template
			) {
				if (Array.isArray(template) && Array.isArray(current)) {
					if (
						template.length > 0 &&
						typeof template[0] === 'object' &&
						!Array.isArray(template[0])
					) {
						return current.map((item) =>
							this.normalizeConfig(item, template[0]),
						);
					}
					return current;
				}
				return current;
			}
			return template;
		}

		const result: any = {};

		for (const key in template) {
			if (template.hasOwnProperty(key)) {
				result[key] = this.normalizeConfig(
					current?.[key],
					template[key],
				);
			}
		}

		return result;
	}
}

export interface Config {
	router: ConfigRouter;
	webServer: ConfigWebServer;
	unity: ConfigUnity;
	cameraRunner: ConfigCameraRunner;
	twilio: ConfigTwilio;
	support: ConfigSupport;
}

export interface ConfigRouter {
	ip: string;
	waitForStartup: boolean;
}

export interface ConfigWebServer {
	port: number;
}

export interface ConfigUnity {
	executable: {
		path: string;
		arguments: string[];
		startUpDelay?: number;
	};

	webSocket: {
		ip: string;
		port: number;
	};

	heartbeatInterval: number;
	calibrationImageInterval: number;
	launchOnStartup: boolean;
}

export interface ConfigCameraRunner {
	webSocket: {
		ip: string;
		port: number;
	};

	pollInterval: number;
}

export interface ConfigTwilio {
	accountSid: string;
	authToken: string;
	fromNumber: string;
	toNumbers: string[];
	aggregateTimeout: number;
	maxMessagesPerDay: number;
}

export interface ConfigSupport {
	telephone: string;
}
