/*
 * Copyright (C) 2024 Tildeslash Ltd. All rights reserved.
 */

class Service {
    /**
     * Represents a Service configuration.
     * Handles validation, serialization, and communication with the backend.
     * @param {Object} data - Initial data for the service.
     * @param {Object} viewModel - Reference to the parent ViewModel.
     */
    constructor(data = {}, viewModel) {
        this.viewModel = viewModel;
        this.id = ko.observable(data.id || null);
        this.name = ko.observable(data.name || '').extend({ required: true, maxLength: 64 });
        this.description = ko.observable(data.description || '').extend({ required: false, maxLength: 255 });
        this.endpoint = ko.observable(data.endpoint || '').extend({ required: false, maxLength: 255 });
        this.apikey = ko.observable(data.apikey || '').extend({ required: false });
        this.script = ko.observable(data.script || '').extend({ required: true });
        this.editingName = ko.observable(false).extend({ throttle: 200 });
        this.showTestResult = ko.observable(false);
        this.mouseover = ko.observable(ISTABLET);
        this.initialState = {};

        this.changeTracker = ko.observable();

        // Define a mapping of ids to icon names
        this.iconMapping = {
            1: 'slack.png',
            2: 'discord.png',
            3: 'pagerduty.png'
        };
        
        // Add a computed observable for the icon
        this.iconSrc = ko.computed(() => {
            const id = this.id();
            if (id && this.iconMapping[id]) {
                return `../../img/logos/${this.iconMapping[id]}`;
            }
            return '../../img/logos/default.png';
        });

        this.isModified = ko.pureComputed(() => {
            const currentState = this.serialize();
            this.changeTracker();
            return JSON.stringify(this.initialState) !== JSON.stringify(currentState);
        });

        // Subscription to automatically hide the test result area if the script is empty
        this.script.subscribe((text) => {
            if (text.trim() === '') {
                this.showTestResult(false);
            }
        });

        if (data.id) {
            this.setInitialState();
        }
    }

    serialize = () => {
        return {
            id: this.id(),
            name: this.name(),
            description: this.description(),
            endpoint: this.endpoint(),
            apikey: this.apikey(),
            script: this.script()
        };
    };

    setInitialState = () => {
        this.initialState = this.serialize();
        this.showTestResult(false);
    };

    resetValidationState = (observable) => {
        observable.isModified(false);
        observable.setError(null);
    };

    update = async () => {
        if (!this.viewModel.services.loading) {
            if (this.validate()) {
                this.editingName(false);
                const url = this.id() ? "../../admin/alert/service/update" : "../../admin/alert/service/create";
                try {
                    const data = await $.ajax({ 
                        url, 
                        data: this.serialize(), 
                        type: 'POST', 
                        dataType: 'json' 
                    });
                    if (!this.id()) this.id(data.id);
                    this.setInitialState();
                    // Make sure isModified is called with the new saved value and is false
                    // by changing changeTracker observable
                    this.changeTracker.valueHasMutated();
                    this.viewModel.services.collapseRow(this);
                    this.viewModel.services.createInProgress(false);
                } catch (error) {
                    MMONIT.error.json(error);
                }
            } else {
                this.viewModel.services.expandedRow(this);
                $('#description' + this.id() + ' .description').focus();
            }
        }
    };

    test = async () => {
        if (this.validate()) {
            if (this.testInProgress && this.testInProgress.readyState != 4) {
                this.testInProgress.abort();
            }
            this.showTestResult(true);
            const testresult = $(`#service${this.id()} #testoutput`);
            testresult.text('Processing...');
            try {
                const data = await $.ajax({ 
                    url: "../../admin/alert/service/test", 
                    data: this.serialize(), 
                    type: 'POST', 
                    dataType: 'json', 
                    timeout: 5000 
                });
                testresult.text('Test message successfully sent');
            } catch (error) {
                if (error.statusText === "timeout") {
                    testresult.text("Test timed out");
                } else if (error.status === 0) {
                    testresult.text("Connection failed. Please check your network or mmonit server.");
                } else if (error.statusText !== "abort") {
                    testresult.text(error.responseText || "An unknown error occurred.");
                }
            }
        }
    };

    editName = (_, e) => {
        e.stopPropagation();
        this.initialState.name = this.name();
        this.editingName(true);
    };

    stopEditName = () => {
        if (this.name().trim().length > 0) {
            this.editingName(false);
            if (this.initialState.name !== this.name()) {
                this.update();
                // This is a first save, focus on the description field
                setTimeout(() => {
                    $(`#description${this.id()} .descSentinel`).focus();
                }, 0);
            }
        }
    };

    validate = () => {
        return (this.name.isValid() && this.script.isValid());
    };
}

class ServiceList {
    /**
     * Manages a list of Service instances.
     * Handles the creation, deletion, and expansion of service entries.
     * @param {Object} viewModel - Reference to the parent ViewModel.
     */
    constructor(viewModel) {
        this.viewModel = viewModel;
        this.loading = true;
        this.list = ko.observableArray();
        this.currentService = ko.observable();
        this.expandedRow = ko.observable();
        this.createInProgress = ko.observable(false);
    }

    toggleExpand = (row, e) => {
        e.stopPropagation();
        this.expandedRow((this.expandedRow() !== row) ? row : null);
    };

    collapseRow = (row) => {
        if (this.expandedRow() === row) {
            row.setInitialState();
            this.expandedRow(null);
        }
    };

    isExpanded = (row) => {
        return row === this.expandedRow();
    };

    enableMouseover = (row, e) => {
        e.stopPropagation();
        if (!ISTABLET) row.mouseover(true);
    };

    disableMouseover = (row, e) => {
        e.stopPropagation();
        if (!ISTABLET) row.mouseover(false);
    };

    confirmDelete = (row, e) => {
        e.stopPropagation();
        this.currentService(row);
        $('#confirmServiceDelete').modal('show');
    };

    add = (_, e) => {
        e.stopPropagation();
        if (!this.createInProgress()) {
            this.createInProgress(true);
            this.expandedRow(null);
            const newService = new Service({}, this.viewModel);
            newService.editingName(true);
            this.list.push(newService);
        }
    };

    remove = async (row, e) => {
        e.stopPropagation();
        if (row.id() > 0) {
            try {
                await $.ajax({
                    url: "../../admin/alert/service/delete",
                    data: { id: row.id() },
                    type: 'POST',
                    timeout: 5000
                });
                $('#confirmServiceDelete').modal('hide');
                this.list.remove(row);
            } catch (jqXHR) {
                $('#confirmServiceDelete').modal('hide');
                MMONIT.error.json(jqXHR);
            }
        } else {
            $('#confirmServiceDelete').modal('hide');
            this.createInProgress(false);
            this.list.remove(row);
        }
    };

    loadServices = async () => {
        try {
            const data = await $.getJSON("../../admin/alert/service/list");
            data.forEach(item => {
                const service = new Service(item, this.viewModel);
                this.list.push(service);
            });
            this.loading = false;
        } catch (jqXHR) {
            MMONIT.error.json(jqXHR);
        }
    };
}

(($) => {
    /**
     * Initializes the main ViewModel.
     * Sets up the ServiceList and binds the ViewModel to the DOM.
     */
    const ViewModel = function () {
        this.width = ko.observable($('#main').outerWidth());
        this.services = new ServiceList(this);
        this.services.loadServices();
    };

    ko.validation.init({
        insertMessages: false,
        decorateElement: true,
        errorElementClass: 'error'
    }, true);

    // Create and bind the ViewModel instance
    const viewModelInstance = new ViewModel();
    ko.applyBindings(viewModelInstance, document.getElementById('serviceTab'));

    $(window).on('resize orientationchange', () => {
        viewModelInstance.width($('#main').outerWidth());
    });
})(jQuery);

