import { ClusterBehavior } from '@matter/main';

import { ISYDeviceNode } from 'isy-nodejs/Devices/ISYDeviceNode';
import { type DeviceNode, type Driver, type Factory, CompositeDevice, Converter } from 'isy-nodejs/ISY';
import { ISYDevice } from 'isy-nodejs/ISYDevice';
import type { DriversOf } from 'isy-nodejs/ISYNode';
import type { Constructor } from 'type-fest';
import winston from 'winston';
import { type ClusterMapping, hasPath } from '../Mappings/MappingRegistry.js';
import { ISYBridgedDeviceBehavior } from './ISYBridgedDeviceBehavior.js';

// #region Type aliases (6)

export type ClusterForBehavior<B> = B extends ClusterBehavior.Type<infer C, infer D, infer E> ? C : never;
export interface DeviceBehavior<P extends CompositeDevice.Any | DeviceNode.Any, T extends { cluster? }> {
	device: P;

	bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>;

	logger: winston.Logger;

	//ts-ignore
	map: ClusterMapping<T['cluster'], P>;

	handlePropertyChange(chg: PropertyChange<P>): void;
}
type NotUnknown<T extends ClusterBehavior> = T extends { cluster: { name: 'Unknown' } } ? never : T;
export type PropertyChange<P extends CompositeDevice.Any | DeviceNode.Any> =
	P extends DeviceNode.Any ?
		{
			driver: keyof P['drivers'];
			newValue: any;
			oldValue: any;
			formattedValue: string;
		}
	: P extends CompositeDevice.Any ? { node: keyof P['nodes']; driver: keyof P['nodes'][keyof P['nodes']]['drivers']; newValue: any; oldValue: any; formattedValue: string }
	: never;

// #endregion Type aliases (6)

// #region Functions (1)

function clusterBehaviorForNode<T extends Constructor<ClusterBehavior> & { cluster }, P extends DeviceNode.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: typeof p } {
	//@ts-ignore
	let s = class ISYClusterBehavior extends base implements DeviceBehavior<P, T> {
		_device: P;

		static factory = p;

		handlers: { [x in DriversOf<P>]: (newValue, oldValue, formattedValue) => void } = {} as any;

		_bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>;

		get bridgedDeviceBehavior(): ISYBridgedDeviceBehavior<P> {
			return this._bridgedDeviceBehavior ?? this.agent.get(ISYBridgedDeviceBehavior<P>);
		}
		///public map: ClusterMapping<ToClusterTypeByName<ClusterForBehavior<ConstructedType<typeof base>>["name"]>,ISYDeviceNode<any, any, any>>;

		map: ClusterMapping<T['cluster'], P>;

		setStateSafe(key: string, value: any) {
			if (this.state[key] != value) {
				try {
					this.state[key] = value;
				} catch (e) {
					this.logger.error(`Error setting state ${key} to ${value}: ${e}`);
				}
			}
		}

		override async initialize(_options?: {}) {
			await super.initialize(_options);

			var behavior = (await this.agent.load(ISYBridgedDeviceBehavior<P>)) as ISYBridgedDeviceBehavior<P>;
			this._bridgedDeviceBehavior = behavior;
			//var behavior = this.agent.get(ISYBridgedDeviceBehavior);
			this._device = behavior.device as P & ISYDeviceNode.Any;

			this.logger.debug(`Initializing cluster behavior: ${this.constructor.name}`);
			//@ts-ignore

			this.map = behavior.mapForBehavior<T, P>(this);

			for (const key2 in this.map?.attributes) {
				let val = this.map.attributes[key2];
				let that = this;

				let driverObj = null as Driver<any, any>;
				if (typeof val === 'string' || typeof val === 'number') {
					driverObj = this._device.drivers[val] as Driver<any, any>;
					if (driverObj) {
						let evt = `${driverObj.name}Changed`;
						//this.state[key2 as string] = driverObj.value;
						//this.state[key2 as string] = driverObj.value;
						this.setStateSafe(key2, driverObj.value);
						this[`handle_${evt}`] = function ({ driver, newValue, oldValue, formattedValue }) {
							this.logger.debug(`${that.constructor.name}: handling property change for ${String(driver)} from ${oldValue} to ${newValue}`);
							this.setStateSafe(key2, newValue);
						};
						this.reactTo(behavior.events[evt], this[`handle_${evt}`]);
					}
				} else if (hasPath(val)) {
					driverObj = this._device.drivers[val.driver as string] as Driver<any, any>;
					if (driverObj) {
						let evt = `${driverObj.name}Changed`;

						let { driver, converter } = val;
						const convFunc = typeof val.converter === 'function' ? val.converter : Converter.get(val.converter as any)?.to;
						if (!convFunc) throw new Error(`Converter ${converter} not found`);
						/*this.state[key2 as string] = convFunc(this._device.drivers[driver as string].value);*/
						this.setStateSafe(key2, convFunc(this._device.drivers[driver as string].value));
						this[`handle_${evt}`] = function ({ driver, newValue, oldValue, formattedValue }) {
							let v = convFunc(newValue);
							let oldV = this.state[key2 as string];
							this.logger.debug(`${that.constructor.name}: handling property change for ${String(driver)} from ${oldValue} (${oldV}) to ${newValue} (${v})`);
							this.setStateSafe(key2, v);
						};
						this.reactTo(behavior.events[evt], this[`handle_${evt}`]);
					}
				}

				/*this.handlers[driverObj.id] = function (parent = that, newValue, oldValue, formattedValue) {
						//this.device.logger(`Handling property change for ${driver} (${key2}) with value ${newValue}`);
						//if (convFunc) this.state[key2 as string] = convFunc(newValue);
						parent.state[key2 as string] = convFunc(newValue);
					};*/

				//(this as any).evt = this.handlers[driverObj.name];
			}
			this.logger.debug(`Cluster behavior initialized: ${this.constructor.name}`);

			//this.reactTo(behavior.events.propertyChanged, this.handlePropertyChange, { lock: false });

			//this._device.on("PropertyChanged", this.handlePropertyChange.bind(this));
		}

		get device(): P & ISYDeviceNode.Any {
			return (this._device = this._device ?? (this.agent.get(ISYBridgedDeviceBehavior<P>).device as P & ISYDeviceNode.Any)) as P & ISYDeviceNode.Any;
		}

		get logger() {
			return this.bridgedDeviceBehavior.logger;
		}

		async handlePropertyChange({ driver, newValue, oldValue, formattedValue }: PropertyChange<P>) {
			// for (const key2 in this.map.attributes) {
			//await this.initialize();
			this.device.logger(`${this.constructor.name}: handling property change for ${String(driver)} with value ${newValue}`);
			if (this.handlers[driver]) {
				this.handlers[driver](newValue, oldValue, formattedValue);
			}
			//   if (typeof this.map.attributes[key2] === "string") {
			//     if(this.map.attributes[key2] == driver)
			//     {
			//         this.state[key2 as string] = newValue;
			//         return;

			//   } }else if (this.map.attributes[key2].driver == driver) {
			//     if (this.map.attributes[key2]?.driver == driver) {
			//       this.state[key2 as string] = this.map.attributes[key2].converter(newValue);

			//     }
			//   }
			// }
		}
	};

	s.factory = p;
	return s;
}

function clusterBehaviorForComposite<T extends Constructor<ClusterBehavior> & { cluster }, P extends CompositeDevice.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: typeof p } {
	let s = class ISYClusterBehavior extends base implements DeviceBehavior<P, T> {
		_device: P;

		static factory = p;

		handlers: {
			[x in keyof P['drivers']]: {
				[y in keyof P['drivers'][x]]: (newValue, oldValue, formattedValue) => void;
			};
		} = {} as any;

		setStateSafe(key: string, value: any) {
			if (this.state[key] != value) {
				try {
					this.state[key] = value;
				} catch (e) {
					this.logger.error(`Error setting state ${key} to ${value}: ${e}`);
				}
			}
		}

		_bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>;

		get bridgedDeviceBehavior(): ISYBridgedDeviceBehavior<P> {
			return this._bridgedDeviceBehavior ?? this.agent.get(ISYBridgedDeviceBehavior<P>);
		}
		///public map: ClusterMapping<ToClusterTypeByName<ClusterForBehavior<ConstructedType<typeof base>>["name"]>,ISYDeviceNode<any, any, any>>;

		map: ClusterMapping<T['cluster'], P>;

		override async initialize(_options?: {}) {
			await super.initialize(_options);

			var behavior = (await this.agent.load(ISYBridgedDeviceBehavior<P>)) as ISYBridgedDeviceBehavior<P>;
			this._bridgedDeviceBehavior = behavior;
			//var behavior = this.agent.get(ISYBridgedDeviceBehavior);
			this._device = behavior.device as P & CompositeDevice.Any;

			this.logger.debug(`Initializing cluster behavior: ${this.constructor.name}`);
			//@ts-ignore

			this.map = behavior.mapForBehavior<T, P>(this);

			for (const key in this.map?.attributes) {
				let val = this.map.attributes[key];

				let driverObj = null as Driver<any, any>;
				let nodeObj = null as DeviceNode.Any;

				if (typeof val === 'string') {
					let [node, id] = val.split('.');

					nodeObj = this._device.nodes[node as string] as DeviceNode.Any;
					driverObj = nodeObj.drivers[id as string] as Driver<any, any>;
					this.setStateSafe(key, nodeObj.drivers[id].value);
					//this.state[key as string] = nodeObj.drivers[id].value;
					let evt = `${node}.${driverObj.name}Changed`;

					this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) {
						this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${driver} (${key}) with value ${newValue}`);
						this.setStateSafe(key, newValue);
					};
					this.reactTo(behavior.events[evt], this[`handle_${evt}`]);
				} else if (hasPath(val)) {
					let driverObj = this._device?.nodes[val.node]?.drivers[val.driver] as Driver<any, any>;
					if (driverObj) {
						let evt = `${val.node}.${driverObj.name}Changed`;

						if (val.converter) {
							const convFunc = typeof val.converter === 'function' ? val.converter : Converter.get(val.converter as any)?.to;
							if (!convFunc) throw new Error(`Converter ${val.converter} not found`);
							this.setStateSafe(key, convFunc(driverObj.value));
							//this.state[key as string] = convFunc(driverObj.value);
							this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) {
								let v = convFunc(newValue);
								let oldV = this.state[key as string];
								this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${String(driver)} from ${oldValue} (${oldV}) to ${newValue} (${v})`);
								this.setStateSafe(key, v);
							};
						} else {
							this.setStateSafe(key, driverObj.value);
							//this.state[key as string] = driverObj.value;
							this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) {
								this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${driver} (${key}) from ${oldValue} to ${newValue}`);
								this.setStateSafe(key, newValue);
							};
						}

						this.reactTo(behavior.events[evt], this[`handle_${evt}`]);
					}
				}
				/*if (driverObj && nodeObj) {
					let evt = `${nodeObj}.${driverObj.name}Changed`;
					(this as any).evt = this.handlers[driverObj.name];
					//this.reactTo(behavior.events[evt], this.handlePropertyChange, { lock: false });
				}*/
				this.logger.debug(`Cluster behavior initialized: ${this.constructor.name}`);
			}

			//this.reactTo(behavior.events.propertyChanged, this.handlePropertyChange, { lock: false });

			//this._device.on("PropertyChanged", this.handlePropertyChange.bind(this));
		}

		get device(): P & ISYDeviceNode.Any {
			return (this._device = this._device ?? (this.agent.get(ISYBridgedDeviceBehavior<P>).device as P & ISYDeviceNode.Any)) as P & ISYDeviceNode.Any;
		}

		get logger() {
			return this.bridgedDeviceBehavior.logger;
		}

		async handlePropertyChange({ node, driver, newValue, oldValue, formattedValue }: PropertyChange<P>) {
			// for (const key2 in this.map.attributes) {
			//await this.initialize();
			this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${String(driver)} with value ${newValue}`);
			if (this.handlers[node][driver as string]) {
				this.handlers[node][driver as string](newValue, oldValue, formattedValue);
			}
			//   if (typeof this.map.attributes[key2] === "string") {
			//     if(this.map.attributes[key2] == driver)
			//     {
			//         this.state[key2 as string] = newValue;
			//         return;

			//   } }else if (this.map.attributes[key2].driver == driver) {
			//     if (this.map.attributes[key2]?.driver == driver) {
			//       this.state[key2 as string] = this.map.attributes[key2].converter(newValue);

			//     }
			//   }
			// }
		}
	};

	s.factory = p;
	return s;
}
export function ISYClusterBehavior<T extends Constructor<ClusterBehavior> & { cluster } & Pick<ClusterBehavior.Type, 'alter'>, P extends CompositeDevice.Any | DeviceNode.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: Factory<P> } {
	let alterations = { attributes: {} };
	for (const l in base.cluster.attributes) {
		alterations.attributes[l] = { persistent: false };
	}
	base = (base as unknown as ClusterBehavior.Type).alter(alterations) as unknown as T;
	if (ISYDevice.isNode(p)) {
		//@ts-ignore
		return clusterBehaviorForNode(base, p);
	} else if (ISYDevice.isComposite(p)) {
		//@ts-ignore
		return clusterBehaviorForComposite(base, p);
	}
}

// #endregion Functions (1)
