import { MatterFlowError } from '@matter/general';
import { AdministratorCommissioning, GeneralCommissioning } from '@matter/main/clusters';
import type { ServerNode } from '@matter/node';
import { AdministratorCommissioningServer, GeneralCommissioningServer } from '@matter/node/behaviors';
import { DeviceCommissioner, FabricManager, SessionManager, type CommissioningMode, type SecureSession } from '@matter/protocol';

import { ServerNodeFailsafeContext } from './ServerNodeFailsafeContext.js';

const SuccessResponse = { errorCode: GeneralCommissioning.CommissioningError.Ok, debugText: '' };
//TODO: add devices after commissioning
export const SuccessResponseWithCommissioningMode = (mode: CommissioningMode) => ({
	errorCode: GeneralCommissioning.CommissioningError.Ok,
	debugText: '',
	commissioningMode: mode
});

export class SlowGeneralCommissioningBehavior extends GeneralCommissioningServer {
	override initialize() {
		const bci = this.state.basicCommissioningInfo;

		if (bci.failSafeExpiryLengthSeconds === undefined) {
			// One minute
			bci.failSafeExpiryLengthSeconds = 60;
		}

		if (bci.maxCumulativeFailsafeSeconds === undefined) {
			// 5 minutes, recommended by spec
			bci.maxCumulativeFailsafeSeconds = 900;
		}

		this.state.breadcrumb = 0;

		const sessionManager = this.env.get(SessionManager);
		this.reactTo(sessionManager.sessions.added, this.#handleAddedPaseSessions);
	}

	/** As required by Commissioning Flows any new PASE session needs to arm the failsafe for 60s. */
	async #handleAddedPaseSessions(session: SecureSession) {
		if (
			!session.isPase || // Only PASE sessions
			session.fabric !== undefined // That does not have an assigned fabric (can never happen in real usecases)
		) {
			return;
		}

		await this.#armFailSafe({ breadcrumb: this.state.breadcrumb, expiryLengthSeconds: this.state.basicCommissioningInfo.failSafeExpiryLengthSeconds }, session);
	}

	async #armFailSafe({ breadcrumb, expiryLengthSeconds }: GeneralCommissioning.ArmFailSafeRequest, session: SecureSession) {
		const commissioner = this.env.get(DeviceCommissioner);

		try {
			// If the fail-safe timer is not currently armed, the commissioning window is open, and the command was
			// received over a CASE session, the command SHALL leave the current fail-safe state unchanged and
			// immediately respond with an ArmFailSafeResponse containing an ErrorCode value of BusyWithOtherAdmin. This
			// is done to allow commissioners, which use PASE connections, the opportunity to use the failsafe during
			// the relatively short commissioning window.
			if (!commissioner.isFailsafeArmed && this.agent.get(AdministratorCommissioningServer).state.windowStatus !== AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen && !session.isPase) {
				throw new MatterFlowError('Failed to arm failsafe using CASE while commissioning window is opened.');
			}

			if (commissioner.isFailsafeArmed) {
				await commissioner.failsafeContext.extend(session.fabric, expiryLengthSeconds);
			} else {
				// If ExpiryLengthSeconds is 0 and the fail-safe timer was not armed, then this command invocation SHALL lead
				// to a success response with no side effect against the fail-safe context.
				if (expiryLengthSeconds === 0) return SuccessResponse;

				await commissioner.beginTimed(
					new ServerNodeFailsafeContext(this.endpoint as ServerNode, {
						fabrics: this.env.get(FabricManager),
						sessions: this.env.get(SessionManager),
						expiryLengthSeconds,
						maxCumulativeFailsafeSeconds: this.state.basicCommissioningInfo.maxCumulativeFailsafeSeconds,
						associatedFabric: session.fabric
					})
				);
			}

			if (commissioner.isFailsafeArmed) {
				// If failsafe is armed after the command, set breadcrumb (not when expired)
				this.state.breadcrumb = breadcrumb;
			}
		} catch (error) {
			MatterFlowError.accept(error);

			//logger.debug(`Error while arming failSafe timer`, error);
			return {
				errorCode: GeneralCommissioning.CommissioningError.BusyWithOtherAdmin,
				debugText: error.message
			};
		}

		return SuccessResponse;
	}

	override armFailSafe(request: GeneralCommissioning.ArmFailSafeRequest) {
		return this.#armFailSafe(request, this.session);
	}
}
