import * as path from 'path'
import { writeFile } from 'fs';
import { Undecked } from './Core';
import { Log } from './Logger';
import { Integration_Connection_Field } from './Integrations/IntegrationsManager';
import { ConnectionValidatorAPI } from './Integrations/IntegrationApi';

import * as fs from 'fs-extra';
var { ensureDir, pathExists, readJson } = fs;



declare var Undecked: Undecked;

export class ConnectionManager {
	managerConfigPath: string;

	connections: ConnectionManager_Connections;

	constructor() { }

	load(callback: () => void) {
		this.managerConfigPath = path.join(Undecked.dataPath, 'connections.json');

		this.connections = {};

		this.loadConfig(() => {
			var integration_count = 0;
			var device_count = 0;
			for (var integrationID in this.connections) {
				integration_count++;
				device_count = device_count + Object.keys(this.connections[integrationID]).length;
			}

			Log('info', `Loaded ${device_count} device(s) across ${integration_count} integration(s)`);
			callback();
		});
	}

	loadConfig(callback: () => void) {
		pathExists(this.managerConfigPath, (err, exists) => {
			if (err) throw err;
			if (exists) {
				readJson(this.managerConfigPath, (err, json) => {
					if (err) throw err;
					this.connections = json.connections;

					for (var integrationID in this.connections)
						for (var connectionType in this.connections[integrationID])
							for (var connectionID in this.connections[integrationID][connectionType]) {
								var connection = this.connections[integrationID][connectionType][connectionID];

								this.validate(
									integrationID,
									connectionType,
									connectionID,
									connection.properties,
									(valid: boolean, error?: string) => {
										Log(
											valid ? 'info' : 'warn',
											valid
												? `Loaded ${connectionType} '${connection.name}' from '${integrationID}'`
												: `Unable to load ${connectionType} '${connection.name}' from '${integrationID}'`
										);
									}
								);
							}

					callback();
				});
			} else {
				this.connections = defaultConnectionConfig.connections;
				this.saveConfig(callback);
			}
		});
	}

	saveConfig(callback?: () => void) {
		var connections = {};
		for (var integrationID in this.connections) {
			connections[integrationID] = {};
			for (var connectionType in this.connections[integrationID]) {
				connections[integrationID][connectionType] = {};
				for (var connectionID in this.connections[integrationID][connectionType]) {
					connections[integrationID][connectionType][connectionID] = this.connections[integrationID][
						connectionType
					][connectionID];

					if (connections[integrationID][connectionType][connectionID].instance != undefined)
						delete connections[integrationID][connectionType][connectionID].instance;
				}
			}
		}
		var toSave: ConnectionManager_Config = {
			connections
		};

		for (var integrationID in toSave.connections)
			for (var connectionType in toSave.connections[integrationID])
				for (var connectionID in toSave.connections[integrationID][connectionType])
					if (toSave.connections[integrationID][connectionType][connectionID].instance != undefined)
						delete toSave.connections[integrationID][connectionType][connectionID].instance;

		writeFile(this.managerConfigPath, JSON.stringify(toSave, null, 4), (err) => {
			if (err) Log('error', 'Error whilst saving device manager config', err.message);
			if (callback) callback();
		});
	}

	create(
		integrationID,
		connectionType: string,
		properties: ConnectionManager_Connections_Properties,
		callback: (valid: boolean, error?: string) => void
	) {
		var _this = this;

		if (this.connections == undefined) this.connections = {};
		if (this.connections[integrationID] == undefined) this.connections[integrationID] = {};
		if (this.connections[integrationID][connectionType] == undefined)
			this.connections[integrationID][connectionType] = {};

		var connectionID = Undecked.generateRandom(8, (checkValid: string) => {
			return _this.connections[integrationID][connectionType][checkValid] == undefined;
		});

		this.validate(integrationID, connectionType, connectionID, properties, (valid, error) => {
			if (valid == true) {
				var name = <string>properties._internal_name;
				delete properties._internal_name;

				var instance =
					_this.connections[integrationID][connectionType] != undefined &&
						_this.connections[integrationID][connectionType][connectionID] != undefined &&
						_this.connections[integrationID][connectionType][connectionID].instance != undefined
						? _this.connections[integrationID][connectionType][connectionID].instance
						: null;
				_this.connections[integrationID][connectionType][connectionID] = {
					connectedSince: Date.now(),
					lastSeen: Date.now(),
					connectionID,
					name,
					online: true,
					properties,
					instance
				};

				Undecked.SocketServer.broadcastTo('home', 'connectedlist', this.getList());

				_this.saveConfig();
				callback(true);
			} else callback(false, error);
		});
	}

	validate(
		integrationID,
		connectionType: string,
		connectionID: string,
		properties: ConnectionManager_Connections_Properties,
		callback: (valid: boolean, error?: string) => void
	) {
		var _this = this;
		if (Undecked.Integrations.exists(integrationID)) {
			var integration = Undecked.Integrations.get(integrationID);

			if (integration.api.hasValidator(connectionType)) {
				var validator = integration.api.getValidator(connectionType);

				var connectionValidatorAPI: ConnectionValidatorAPI = {
					properties: properties,
					callback,
					instance: _this.getConnectionInstance(integrationID, connectionType, connectionID),
					setInstance(instance) {
						_this.setConnectionInstance(integrationID, connectionType, connectionID, instance);
					}
				};
				validator(connectionValidatorAPI);
			} else callback(false, `No validator found for connection type ${connectionType}`);
		}
	}

	getConnectionRequestData(
		integrationID: string,
		connectionType: string
	): { fields: Integration_Connection_Field[]; message?: string; link?: { address: string; title: string } } {
		if (Undecked.Integrations.exists(integrationID)) {
			var integrationwrapper = Undecked.Integrations.get(integrationID);
			if (integrationwrapper.integration.connections != undefined) {
				for (let i = 0; i < integrationwrapper.integration.connections.length; i++) {
					var connection = integrationwrapper.integration.connections[i];
					if (connection.type == connectionType)
						return { fields: connection.fields, message: connection.message, link: connection.link };
				}
			}
		}
		return null;
	}

	getList(): ConnectionManager_ListItem[] {
		var list: ConnectionManager_ListItem[] = [];
		for (var integrationID in this.connections) {
			if (Undecked.Integrations.exists(integrationID)) {
				var integration = Undecked.Integrations.get(integrationID);

				for (var connectionType in this.connections[integrationID]) {
					for (var connectionID in this.connections[integrationID][connectionType]) {
						var connection = this.connections[integrationID][connectionType][connectionID];
						list.push({
							connectionID,

							integrationName: integration.integration.name,
							connectionType: integration.connectionsmap[connectionType].name,
							name: connection.name,
							online: connection.online
						});
					}
				}
			}
		}
		return list;
	}

	getConnections(integrationID: string, connectionType: string): { id: string; text: string }[] {
		var connections = [];
		if (this.connections != undefined) {
			if (this.connections[integrationID] != undefined) {
				if (this.connections[integrationID][connectionType] != undefined) {
					for (var connectionID in this.connections[integrationID][connectionType]) {
						var connection = this.connections[integrationID][connectionType][connectionID];
						connections.push({ id: connection.connectionID, text: connection.name });
					}
				}
			}
		}

		return connections;
	}

	getConnection(integrationID: string, connectionType: string, connectionID: string): ConnectionManager_Connection {
		if (this.connections != undefined) {
			if (this.connections[integrationID] != undefined) {
				if (this.connections[integrationID][connectionType] != undefined) {
					if (this.connections[integrationID][connectionType][connectionID] != undefined) {
						return this.connections[integrationID][connectionType][connectionID];
					}
				}
			}
		}

		return null;
	}

	setConnectionInstance(integrationID: string, connectionType: string, connectionID: string, instance: any) {
		if (this.connections == undefined) this.connections = {};
		if (this.connections[integrationID] == undefined) this.connections[integrationID] = {};
		if (this.connections[integrationID][connectionType] == undefined)
			this.connections[integrationID][connectionType] = {};
		if (this.connections[integrationID][connectionType][connectionID] == undefined)
			this.connections[integrationID][connectionType][connectionID] = <any>{};
		this.connections[integrationID][connectionType][connectionID].instance = instance;
		return true;
	}

	getConnectionInstance(integrationID: string, connectionType: string, connectionID: string) {
		if (this.connections != undefined) {
			if (this.connections[integrationID] != undefined) {
				if (this.connections[integrationID][connectionType] != undefined) {
					if (this.connections[integrationID][connectionType][connectionID] != undefined) {
						return this.connections[integrationID][connectionType][connectionID].instance;
					}
				}
			}
		}

		return null;
	}
}

var defaultConnectionConfig: ConnectionManager_Config = {
	connections: {}
};

// export interface ConnectionManager {
// 	load: (callback: () => void) => void;
// 	getConnectionRequestData: (integrationID: string, connectionType: string) => Integration_Connection_Field[];
// 	create: (
// 		integrationID,
// 		connectionType: string,
// 		properties: ConnectionManager_Connections_Properties,
// 		callback: (valid: boolean, error?: string) => void
// 	) => void;
// 	validate: (
// 		integrationID,
// 		connectionType: string,
// 		properties: ConnectionManager_Connections_Properties,
// 		callback: (valid: boolean, error?: string) => void
// 	) => void;
// 	getList: () => ConnectionManager_ListItem[];
// 	getConnections: (integrationID: string, connectionType: string) => { id: string; text: string }[];
// 	getConnection: (integrationID: string, connectionType: string, connectionID) => ConnectionManager_Connection;
// }

export interface ConnectionManager_Config {
	connections: ConnectionManager_Connections;
}

export interface ConnectionManager_Connections {
	[integrationID: string]: {
		[connectionType: string]: {
			[connectionID: string]: ConnectionManager_Connection;
		};
	};
}

export interface ConnectionManager_Connection {
	name: string;
	connectedSince: number;
	lastSeen: number;
	online: boolean;
	connectionID: string;
	properties: ConnectionManager_Connections_Properties;
	instance?: any;
}

export interface ConnectionManager_Connections_Properties {
	[key: string]: any;
}

export interface ConnectionManager_ListItem {
	connectionID: string;
	integrationName: string;
	connectionType: string;
	name: string;
	online: boolean;
}
