/**
 * @module node-opcua-client
 */
import chalk from "chalk";

import { assert } from "node-opcua-assert";
import type { TimestampsToReturn } from "node-opcua-data-value";
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
import {
    CreateMonitoredItemsRequest,
    type CreateMonitoredItemsResponse,
    ModifyMonitoredItemsRequest,
    ModifyMonitoredItemsResponse,
    MonitoredItemModifyRequest,
    type MonitoredItemModifyResult,
    type MonitoringMode,
    type SetMonitoringModeResponse
} from "node-opcua-service-subscription";
import { type Callback, type ErrorCallback, type StatusCode, StatusCodes } from "node-opcua-status-code";

import type { MonitoredItemCreateRequestOptions, MonitoringParametersOptions } from "node-opcua-types";
import type { ClientMonitoredItemBase } from "./client_monitored_item_base";
import type { SetMonitoringModeRequestLike } from "./client_session";
import type { ClientSubscription } from "./client_subscription";
import type { ClientMonitoredItemImpl } from "./private/client_monitored_item_impl";
import type { ClientSessionImpl } from "./private/client_session_impl";

const debugLog = make_debugLog(__filename);
const doDebug = checkDebugFlag(__filename);

export interface ClientMonitoredItemBaseEx extends ClientMonitoredItemBase {
    internalSetMonitoringMode(monitoringMode: MonitoringMode): void;
}
/**
 * @internal
 */
export class ClientMonitoredItemToolbox {
    public static _toolbox_monitor(
        subscription: ClientSubscription,
        timestampsToReturn: TimestampsToReturn,
        monitoredItems: ClientMonitoredItemBase[],
        done: ErrorCallback
    ): void {
        assert(typeof done === "function");

        // we expect subscription to be valid and have a valid session
        if (!subscription.hasSession) {
            const err0 = new Error("Invalid subscription");
            if (done) {
                return done(err0);
            }
            return;
        }

        // may be the subscription has been terminated or is not fully initialize, in the meantime
        if (!subscription.isActive) {
            const err1 = new Error("Subscription has been terminated or is not fully initialized");
            if (done) {
                return done(err1);
            }
            return;
        }

        const itemsToCreate: MonitoredItemCreateRequestOptions[] = [];
        for (const monitoredItem of monitoredItems) {
            const monitoredItemI = monitoredItem as ClientMonitoredItemImpl;
            const itemToCreate = monitoredItemI._prepare_for_monitoring();
            if (typeof itemToCreate.error === "string") {
                return done(new Error(itemToCreate.error));
            }
            itemsToCreate.push(itemToCreate as MonitoredItemCreateRequestOptions);
        }

        const createMonitorItemsRequest = new CreateMonitoredItemsRequest({
            itemsToCreate,
            subscriptionId: subscription.subscriptionId,
            timestampsToReturn
        });

        for (let i = 0; i < monitoredItems.length; i++) {
            const monitoredItem = monitoredItems[i] as ClientMonitoredItemImpl;
            monitoredItem._before_create();
        }

        const session = subscription.session as ClientSessionImpl;
        assert(session, "expecting a valid session attached to the subscription ");

        session.createMonitoredItems(createMonitorItemsRequest, (err?: Error | null, response?: CreateMonitoredItemsResponse) => {
            /* c8 ignore next */
            if (err) {
                debugLog(chalk.red("ClientMonitoredItemBase#_toolbox_monitor:  ERROR in createMonitoredItems ", err.message));
            } else {
                /* c8 ignore next */
                if (!response) {
                    return done(new Error("Internal Error"));
                }

                response.results = response.results || [];

                for (let i = 0; i < response.results.length; i++) {
                    const monitoredItemResult = response.results[i];
                    const monitoredItem = monitoredItems[i] as ClientMonitoredItemImpl;
                    monitoredItem._after_create(monitoredItemResult);
                }
            }
            done(err ? err : undefined);
        });
    }

    public static _toolbox_modify(
        subscription: ClientSubscription,
        monitoredItems: ClientMonitoredItemBase[],
        parameters: MonitoringParametersOptions,
        timestampsToReturn: TimestampsToReturn,
        callback: Callback<MonitoredItemModifyResult[]>
    ): void {
        assert(callback === undefined || typeof callback === "function");

        const itemsToModify = monitoredItems.map((monitoredItem: ClientMonitoredItemBase) => {
            const clientHandle = monitoredItem.monitoringParameters.clientHandle;
            assert(clientHandle !== 4294967295);
            return new MonitoredItemModifyRequest({
                monitoredItemId: monitoredItem.monitoredItemId,
                requestedParameters: {
                    ...parameters,
                    clientHandle
                }
            });
        });
        const modifyMonitoredItemsRequest = new ModifyMonitoredItemsRequest({
            itemsToModify,
            subscriptionId: subscription.subscriptionId,
            timestampsToReturn
        });

        const session = subscription.session as ClientSessionImpl;
        assert(session, "expecting a valid session attached to the subscription ");

        session.modifyMonitoredItems(modifyMonitoredItemsRequest, (err: Error | null, response?: ModifyMonitoredItemsResponse) => {
            /* c8 ignore next */
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof ModifyMonitoredItemsResponse)) {
                return callback(new Error("internal error"));
            }

            response.results = response.results || [];

            assert(response.results.length === monitoredItems.length);

            const res = response.results[0];

            /* c8 ignore next */
            if (response.results.length === 1 && res.statusCode.isNotGood()) {
                return callback(new Error("Error" + res.statusCode.toString()));
            }
            callback(null, response.results);
        });
    }

    public static _toolbox_setMonitoringMode(
        subscription: ClientSubscription,
        monitoredItems: ClientMonitoredItemBaseEx[],
        monitoringMode: MonitoringMode,
        callback: Callback<StatusCode[]>
    ): void {
        const monitoredItemIds = monitoredItems.map((monitoredItem) => monitoredItem.monitoredItemId);

        const setMonitoringModeRequest: SetMonitoringModeRequestLike = {
            monitoredItemIds,
            monitoringMode,
            subscriptionId: subscription.subscriptionId
        };

        const session = subscription.session as ClientSessionImpl;
        assert(session, "expecting a valid session attached to the subscription ");

        session.setMonitoringMode(setMonitoringModeRequest, (err: Error | null, response?: SetMonitoringModeResponse) => {
            if (err) {
                return callback(err);
            }
            monitoredItems.forEach((monitoredItem) => {
                monitoredItem.internalSetMonitoringMode(monitoringMode);
            });
            response = response!;
            response.results = response.results || [];
            callback(null, response.results);
        });
    }
}
