import AmbientConfig from "./config/AmbientConfig";
import { socketConfig } from "./config/socketConfig";
import { AmbientRestAPI } from "./api/AmbientRestAPI";
import { Logger } from "./utils/Logger";
import { Guard } from "./utils/Guard";
import { AugnitoRecorder } from "demoaugnitorecorder";
import { AugnitoSocketResponse } from "./support/AugnitoSocketResponse";

/**
 * Augnito Ambient Manager
 * @description Handles the connection with the Ambient API Server.
 */
export class AugnitoAmbient {
    private _logTag = "Augnito-Ambient";
    private _ambientRestAPI: AmbientRestAPI;
    private config: socketConfig | null | undefined;
    private recorderIns: AugnitoRecorder | undefined;
    constructor(config: AmbientConfig) {
        this._ambientRestAPI = new AmbientRestAPI(this.validateConfig(config));
        this.config = this.createSocketConfig(this.validateConfig(config));
    }

    /**
     * @description Callback to receive the JOb Id
     */
    public onJobCreated?: (text: string) => void;

    /**
     * @description Callback for status changed
     */
    public onStateChanged?: (isRecording: boolean) => void;

    /**
     * Callback triggered when an error occurs within the SDK
     */
    public onError?: (errorMessage: string) => void;
    public onOtherResult?: (text: string) => void;
    public onIdleMic?: () => void;
    private initRecorder(_filetype: string, _noteparams: string) {
        console.log(_filetype, _noteparams);
        this.recorderIns = new AugnitoRecorder({
            // "{\"Region\": 801, \"Specialty\": 200, \"NoteType\": 40, \"Gender\": 0}"
            serverURL: this.config?.prepareWSSURL(_filetype, _noteparams) || "",
            enableLogs: true,
            isDebug: false,
            bufferInterval: 30,
            EOS_Message: "AMBIENTSDKEOS",
            socketTimeoutInterval: 10000,
        });
        this.recorderIns.onError = this.onErrorCallback.bind(this);
        this.recorderIns.onStateChanged = this.onStateChangeCallback.bind(this);
        this.recorderIns.onSessionEvent =
            this.onSessionEventCallback.bind(this);
        this.recorderIns.onOtherResults =
            this.onOtherResultsCallback.bind(this);
    }

    // #region Public Methods

    /**
     * Returns the Note parameters which need be use while doing toggle listeing
     * @returns JSON object of Note parameters
     */
    public async getNoteParams(): Promise<any> {
        try {
            if (!this._ambientRestAPI) {
                Logger.error("SDK not initialized", this._logTag);
                return;
            }
            return await this._ambientRestAPI.GetNoteParams();
        } catch (e: any) {
            if (e instanceof Error) {
                this.onErrorCallback(e.message);
            }
        }
    }

    /**
     * @param JobId to retrieve output for a specific audio file
     * @returns JSON object contains both Transcript and Note on sucess else fail resonse
     */
    public async getSummarizedNote(JobId: string): Promise<any> {
        try {
            if (!this._ambientRestAPI) {
                Logger.error("SDK not initialized", this._logTag);
                return;
            }
            var responseJson = await this._ambientRestAPI.FetchJob(JobId);
            if (responseJson) {
                if (responseJson.Status === 200) {
                    return responseJson;
                } else {
                    this.onErrorCallback(responseJson.ErrorMessage);
                }
            } else {
                this.onErrorCallback("Unknown Error!");
            }
        } catch (e: any) {
            if (e instanceof Error) {
                this.onErrorCallback(e.message);
            }
        }
    }

    /**
     * @param JobId needs to pass to store the final Note for that audio
     * @param NoteDate This key will store the final edited Note that user wants to send.
     * @returns suceess response on successful submit of note else fail response
     */
    public async sendSummarizedNote(
        JobId: string,
        NoteDate: string
    ): Promise<any> {
        try {
            if (!this._ambientRestAPI) {
                Logger.error("SDK not initialized", this._logTag);
                return;
            }
            return await this._ambientRestAPI.SendFinalNote(JobId, NoteDate);
        } catch (e: any) {
            if (e instanceof Error) {
                this.onErrorCallback(e.message);
            }
        }
    }

    /**
     * @param filetype Type of file being uploaded, wav, mp3, etc. Ex: “filetype=wav“
     * @param noteparams Qualifiers to determine the type of clinical note to be generated for that audio file.
     * @returns Callback triggers to reurn the Job id on meta message
     */
    public toggleListening(_filetype: string, _noteparams: string): void {
        console.log("toggleListening:", _filetype, _noteparams);
        if (!this.recorderIns) {
            this.initRecorder(_filetype, _noteparams);
        }
        this.recorderIns?.toggleStartStopAudioStream();
    }

    // #endregion

    // #region client callbacks

    private onEventCallback(data: string): void {
        if (this.onJobCreated) {
            this.onJobCreated(data);
        }
    }

    private onStateChangeCallback(isRecording: boolean): void {
        if (this.onStateChanged) {
            this.onStateChanged(isRecording);
        }
    }

    private onErrorCallback(errorMessage: string): void {
        if (this.onError) {
            this.onError(errorMessage);
        }
    }

    private onOtherResultsCallback(data: string): void {
        if (this.onOtherResult) {
            this.onOtherResult(data);
        }
    }

    private onIdleMicCallback(): void {
        if (this.onIdleMic) {
            this.onIdleMic();
        }
    }
    private onSessionEventCallback(data: string | AugnitoSocketResponse): void {
        if (!data) {
            return;
        }

        let json: any;
        try {
            json = typeof data === "string" ? JSON.parse(data) : data;
        } catch (error) {
            Logger.error(
                `Error parsing session event data: ${error}`,
                this._logTag
            );
            return;
        }

        if (!json || !("Type" in json)) {
            return;
        }

        if (json.Type.toLowerCase() === "meta" && this.config?.onMetaEvent) {
            if (!json.JobID) {
                Logger.error(`JobID is missing in meta event`, this._logTag);
                return;
            }
            this.config.onMetaEvent(json.JobID);
        } else if (json.Type.toLowerCase() === "error" && json.Data) {
            this.onErrorCallback(json.Data);
        }

        if (typeof json.Event !== "object" || json.Event === null) {
            return;
        }

        const { Type: eventType, Value: eventValue } = json.Event;

        if (!eventType) {
            return;
        }

        if (eventType === "SESSION_CREATED" && eventValue) {
            Logger.log(`session Token ${eventValue}`, this._logTag);
        } else if (eventType === "SERVICE_DOWN") {
            Logger.error(eventType, this._logTag);
        } else if (eventType === "NO_DICTATION_STOP_MIC") {
            Logger.log("NO_DICTATION_STOP_MIC", this._logTag);
            this.onIdleMicCallback();
        } else if (eventType === "INVALID_AUTH_CREDENTIALS") {
            Logger.error("INVALID_AUTH_CREDENTIALS", this._logTag);
        } else if (eventType === "LOW_BANDWIDTH") {
            Logger.log(
                "LOW_BANDWIDTH: Check internet connection",
                this._logTag
            );
        }
    }
    // #endregion

    /**
     * Validates the Ambient config has all the mandatory fields
     * @param config The config sent by the client application
     */
    validateConfig(config: AmbientConfig): AmbientConfig {
        Guard.Against.NullOrEmpty(config.server, "Server");
        Guard.Against.NullOrEmpty(config.subscriptionCode, "SubscriptionCode");
        Guard.Against.NullOrEmpty(config.accessKey, "AccessKey");
        Guard.Against.NullOrEmpty(config.userTag, "UserTag");
        return config;
    }

    private createSocketConfig(config: AmbientConfig): socketConfig {
        const _socketConfig = new socketConfig(config);
        // _socketConfig.onStartOfRecording =
        //     this.onStateChangeCallback.bind(this);
        // _socketConfig.onStopOfRecording = this.onStateChangeCallback.bind(this);
        _socketConfig.onError = this.onErrorCallback.bind(this);
        _socketConfig.onMetaEvent = this.onEventCallback.bind(this);
        return _socketConfig;
    }
}
