/*
 * @Author: baozhoutao@steedos.com
 * @Date: 2022-09-15 13:09:51
 * @LastEditors: 孙浩林 sunhaolin@steedos.com
 * @LastEditTime: 2023-10-08 Updated to use new utils
 * @Description: Refactored to use new utils and async/await
 */
const express = require("express");
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const _ = require("lodash");
const {
    insert_instance_tasks,
    update_instance_tasks,
    insert_many_instance_tasks,
} = require('../manager/instance_tasks_manager')
const PermissionManager = require('../manager/permission_manager');
const UUFlowManager = require('../manager/uuflow_manager');
const pushManager = require('../manager/push_manager');
const { _makeNewID, getCollection } = require('../utils/collection');
const { broker } = require("@steedos/objectql");

/**
 * Validate input data
 * @param {Object} data - Input data to validate
 * @throws {Error} If validation fails
 */
function validateInput(data) {
    if (!_.isString(data.instance_id)) {
        throw new Error('instance_id must be a string');
    }
    if (!_.isString(data.space_id)) {
        throw new Error('space_id must be a string');
    }
    if (!_.isString(data.flow_id)) {
        throw new Error('flow_id must be a string');
    }
    if (!_.isBoolean(data.hasSaveInstanceToAttachment)) {
        throw new Error('hasSaveInstanceToAttachment must be a boolean');
    }
    if (!_.isBoolean(data.isForwardAttachments)) {
        throw new Error('isForwardAttachments must be a boolean');
    }
    if (!_.isNil(data.description) && !_.isString(data.description)) {
        throw new Error('description must be a string, null, or undefined');
    }
    if (!_.isArray(data.selectedUsers)) {
        throw new Error('selectedUsers must be an array');
    }
    if (!_.includes(['forward', 'distribute'], data.action_type)) {
        throw new Error('action_type must be either "forward" or "distribute"');
    }
}

router.post("/api/workflow/v2/instance/forward", requireAuthentication, async function (req, res) {
    try {
        const userSession = req.user;
        const current_user_info = userSession;
        const current_user_id = current_user_info.userId;

        const hashData = req.body;
        const {
            instance_id,
            space_id,
            flow_id,
            hasSaveInstanceToAttachment,
            description,
            isForwardAttachments,
            selectedUsers,
            action_type,
            related,
            from_approve_id
        } = hashData;

        validateInput({
            instance_id,
            space_id,
            flow_id,
            hasSaveInstanceToAttachment,
            description,
            isForwardAttachments,
            selectedUsers,
            action_type
        });

        if (action_type == "distribute") {
            if (!_.isString(from_approve_id)) {
                throw new Error('from_approve_id must be a string');
            }
        }

        const instancesCollection = await getCollection('instances');
        const spacesCollection = await getCollection('spaces');
        const usersCollection = await getCollection('users');

        const ins = await instancesCollection.findOne({ _id: instance_id });
        const old_space_id = ins.space;

        const flow = await UUFlowManager.getFlow(flow_id, { fields: { historys: 0 } });
        const space = await spacesCollection.findOne({ _id: space_id }, { projection: { _id: 1 } });

        if (!ins || !flow || !space) {
            throw new Error("record not exists!");
        }

        let forward_users = _.isEmpty(selectedUsers) ? [current_user_id] : selectedUsers;

        // Validate permissions for forward users
        const noPermissionUsers = [];
        for (const uid of forward_users) {
            const permissions = await PermissionManager.getFlowPermissions(flow_id, uid, flow);
            if (!permissions.includes("add")) {
                noPermissionUsers.push(uid);
            }
        }

        if (!_.isEmpty(noPermissionUsers)) {
            const noPermissionUsersNames = [];
            const users = await usersCollection.find(
                { _id: { $in: noPermissionUsers } },
                { projection: { name: 1 } }
            ).toArray();

            users.forEach(u => noPermissionUsersNames.push(u.name));
            throw new Error(`该提交人「${noPermissionUsersNames.join(",")}」没有提交此申请单的权限。`);
        }

        const new_ins_data = [];
        let current_trace = null;

        if (action_type == "distribute") {
            for (const t of ins.traces) {
                if (current_trace) break;
                for (const a of t.approves) {
                    if (a._id == from_approve_id) {
                        current_trace = t;
                        break;
                    }
                }
            }
        } else {
            current_trace = _.last(ins.traces);
        }

        const current_trace_id = current_trace._id;
        const forward_approves = [];
        const from_user = await usersCollection.findOne(
            { _id: current_user_id },
            { projection: { name: 1 } }
        );
        const from_user_name = from_user.name;
        const set_obj = {};

        // Calculate values
        const old_values = ins.values;
        let new_values = {};
        const form = await UUFlowManager.getForm(flow.form, { fields: { historys: 0 } });
        const fields = form.current.fields || [];

        const formCollection = await getCollection('forms')

        const old_form = await formCollection.findOne({ _id: ins.form });
        const old_form_version = ins.form_version;
        let old_fields = [];
        const common_fields = [];
        const select_to_input_fields = [];

        if (old_form.current._id == old_form_version) {
            old_fields = old_form.current.fields;
        } else if (old_form.historys) {
            for (const h of old_form.historys) {
                if (h._id == old_form_version) {
                    old_fields = h.fields;
                    break;
                }
            }
        }

        // Process fields
        for (const field of fields) {
            const exists_field = _.find(old_fields, f => f.type == field.type && f.code == field.code);
            if (exists_field) common_fields.push(field);

            const select_input_field = _.find(old_fields, f => 
                f.type == "select" && field.type == "input" && f.code == field.code
            );
            if (select_input_field) select_to_input_fields.push(select_input_field);
        }

        // Process select to input fields
        for (const field of select_to_input_fields) {
            if (old_values[field.code]) {
                new_values[field.code] = old_values[field.code];
            }
        }

        // Process common fields
        for (const field of common_fields) {
            if (field.type == "section") {
                if (field.fields) {
                    for (const f of field.fields) {
                        if (["group", "user"].includes(f.type) && old_space_id != space_id) {
                            continue;
                        }
                        const key = f.code;
                        let old_v = old_values[key];
                        if (old_v) {
                            const fieldOptions = f.options?.split("\n").map(n => {
                                const itemSplits = n.split(":");
                                return {
                                    label: itemSplits[0],
                                    value: itemSplits[1] || n,
                                };
                            });

                            if (f.type == "select" || f.type == "radio") {
                                const selectedOption = fieldOptions?.find(item => item.value === old_v);
                                if (!selectedOption) continue;
                            }

                            if (f.type == "multiSelect") {
                                const old_multiSelected = old_v.split(",");
                                const new_multiSelected = _.intersection(
                                    fieldOptions?.map(n => n.value) || [],
                                    old_multiSelected
                                );
                                old_v = new_multiSelected.join(",");
                            }

                            new_values[key] = old_v;
                        }
                    }
                }
            } else if (field.type == "table") {
                if (!_.isEmpty(old_values[field.code])) {
                    new_values[field.code] = [];
                    for (const old_table_row_values of old_values[field.code]) {
                        const new_table_row_values = {};

                        if (!_.isEmpty(field.fields)) {
                            for (const f of field.fields) {
                                if (["group", "user"].includes(f.type) && old_space_id != space_id) {
                                    continue;
                                }
                                const key = f.code;
                                let old_v = old_table_row_values[key];
                                if (old_v) {
                                    const fieldOptions = f.options?.split("\n").map(n => {
                                        const itemSplits = n.split(":");
                                        return {
                                            label: itemSplits[0],
                                            value: itemSplits[1] || n,
                                        };
                                    });

                                    if (f.type == "select" || f.type == "radio") {
                                        const selectedOption = fieldOptions?.find(item => item.value === old_v);
                                        if (!selectedOption) continue;
                                    }

                                    if (f.type == "multiSelect") {
                                        const old_multiSelected = old_v.split(",");
                                        const new_multiSelected = _.intersection(
                                            fieldOptions?.map(n => n.value) || [],
                                            old_multiSelected
                                        );
                                        old_v = new_multiSelected.join(",");
                                    }

                                    new_table_row_values[key] = old_v;
                                }
                            }
                        }

                        if (old_table_row_values._id) {
                            new_table_row_values._id = _makeNewID();
                        }

                        if (!_.isEmpty(new_table_row_values)) {
                            new_values[field.code].push(new_table_row_values);
                        }
                    }
                }
            } else {
                if (["group", "user"].includes(field.type) && old_space_id != space_id) {
                    continue;
                }
                const key = field.code;
                let old_v = old_values[key];
                if (old_v) {
                    const fieldOptions = field.options?.split("\n").map(n => {
                        const itemSplits = n.split(":");
                        return {
                            label: itemSplits[0],
                            value: itemSplits[1] || n,
                        };
                    });

                    if (field.type == "select" || field.type == "radio") {
                        const selectedOption = fieldOptions?.find(item => item.value === old_v);
                        if (!selectedOption) continue;
                    }

                    if (field.type == "multiSelect") {
                        const old_multiSelected = old_v.split(",");
                        const new_multiSelected = _.intersection(
                            fieldOptions?.map(n => n.value) || [],
                            old_multiSelected
                        );
                        old_v = new_multiSelected.join(",");
                    }

                    new_values[key] = old_v;
                }
            }
        }

        if (action_type === "distribute") {
            delete new_values.record_need;
            delete new_values.FONDSID;
        }

        // Calculate instance name
        let instance_name = "";
        const name_forumla = form.current.name_forumla;
        if (name_forumla) {
            try {
                const iscript = name_forumla
                    .replace(/\{/g, "(new_values['")
                    .replace(/\}/g, "'] || '')");
                const rev = eval(iscript);
                instance_name = rev || flow.name;
            } catch (error) {
                throw new Error("计算申请单标题出错请检查表单标题脚本。");
            }
        } else {
            instance_name = flow.name;
        }

        const start_step = _.find(flow.current.steps, step => step.step_type == "start");

        if (action_type === "forward") {
            new_values = await UUFlowManager.getApproveValues(
                new_values,
                start_step.permissions,
                flow.form,
                flow.current.form_version
            );
        }

        let category_name = "";
        if (form.category) {
            const category = await UUFlowManager.getCategory(form.category, { fields: { _id: 1, name: 1 } });
            if (category) category_name = category.name;
        }

        const extras = await UUFlowManager.caculateExtras(new_values, form, form.current._id);

        // Process each forward user
        for (const user_id of forward_users) {
            const user_info = await UUFlowManager.getUser(user_id, {
                fields: { name: 1, utcOffset: 1, locale: 1 }
            });
            const space_user = await UUFlowManager.getSpaceUser(space_id, user_id, {
                fields: { organization: 1 }
            });
            const space_user_org_info = await UUFlowManager.getOrganization(
                space_user.organization,
                { fields: { name: 1, fullname: 1 } }
            );

            const now = new Date();
            const ins_obj = {};
            const agent = await UUFlowManager.getAgent(space_id, user_id);

            let handler_id = user_id;
            let handler_info = user_info;
            let handler_space_user = space_user;
            let handler_org_info = space_user_org_info;

            if (agent) {
                handler_id = agent;
                handler_info = await UUFlowManager.getUser(agent, {
                    fields: { name: 1, utcOffset: 1, locale: 1 }
                });
                handler_space_user = await UUFlowManager.getSpaceUser(space_id, agent, {
                    fields: { organization: 1 }
                });
                handler_org_info = await UUFlowManager.getSpaceUserOrgInfo(handler_space_user);
            }

            ins_obj._id = _makeNewID();
            ins_obj.space = space_id;
            ins_obj.flow = flow_id;
            ins_obj.flow_version = flow.current._id;
            ins_obj.form = flow.form;
            ins_obj.form_version = flow.current.form_version;
            ins_obj.name = instance_name;
            ins_obj.submitter = handler_id;
            ins_obj.submitter_name = handler_info.name;
            ins_obj.applicant = user_id;
            ins_obj.applicant_name = user_info.name;
            ins_obj.applicant_organization = space_user.organization;
            ins_obj.applicant_organization_name = space_user_org_info.name;
            ins_obj.applicant_organization_fullname = space_user_org_info.fullname;
            ins_obj.state = "draft";
            ins_obj.code = "";
            ins_obj.is_archived = false;
            ins_obj.is_deleted = false;
            ins_obj.created = now;
            ins_obj.created_by = current_user_id;
            ins_obj.modified = now;
            ins_obj.modified_by = current_user_id;
            ins_obj.inbox_users = [handler_id];
            ins_obj.values = new_values;

            if (action_type == "distribute") {
                ins_obj.distribute_from_instance = ins.distribute_from_instance || instance_id;
                ins_obj.distribute_from_instances = _.clone(ins.distribute_from_instances) || [];
                ins_obj.distribute_from_instances.push(instance_id);

                if (related) {
                    ins_obj.related_instances = [instance_id];
                }
            } else if (action_type == "forward") {
                ins_obj.forward_from_instance = instance_id;
            }

            // Create Trace
            const trace_obj = {
                _id: _makeNewID(),
                instance: ins_obj._id,
                is_finished: false
            };

            // Find start step info
            let step_id, step_name, can_edit_main_attach, can_edit_normal_attach;
            for (const step of flow.current.steps) {
                if (step.step_type == "start") {
                    step_id = step._id;
                    step_name = step.name;
                    can_edit_main_attach = step.can_edit_main_attach;
                    can_edit_normal_attach = step.can_edit_normal_attach;
                    break;
                }
            }

            trace_obj.step = step_id;
            trace_obj.start_date = now;
            trace_obj.name = step_name;

            // Create Approve
            const appr_obj = {
                _id: _makeNewID(),
                instance: ins_obj._id,
                trace: trace_obj._id,
                is_finished: false,
                user: user_id,
                user_name: user_info.name,
                handler: handler_id,
                handler_name: handler_info.name,
                handler_organization: handler_space_user.organization,
                handler_organization_name: handler_org_info.name,
                handler_organization_fullname: handler_org_info.fullname,
                type: "draft",
                start_date: now,
                read_date: now,
                is_read: false,
                is_error: false,
                values: new_values
            };

            if (agent) {
                appr_obj.agent = agent;
            }

            trace_obj.approves = [appr_obj];
            ins_obj.traces = [trace_obj];

            if (flow.auto_remind == true) ins_obj.auto_remind = true;

            ins_obj.current_step_name = start_step.name;
            ins_obj.flow_name = flow.name;

            if (category_name) {
                ins_obj.category_name = category_name;
                ins_obj.category = form.category;
            }

            ins_obj.extras = extras;

            const result = await instancesCollection.insertOne(ins_obj);
            const new_ins_id = result.insertedId;

            await insert_instance_tasks(ins_obj._id, trace_obj._id, appr_obj._id);

            // Handle attachments
            const collection = await getCollection('cfs.instances.filerecord');
            if (hasSaveInstanceToAttachment) {
                try {
                    const instanceHtml = InstanceReadOnlyTemplate.getInstanceHtml(
                        user_info,
                        space_id,
                        ins,
                        { absolute: true, showTrace: true }
                    );

                    const instanceFile = new FS.File();
                    instanceFile.attachData(
                        Buffer.from(instanceHtml, "utf-8"),
                        { type: "text/html" },
                        async (error) => {
                            if (error) throw new Error(error.reason);

                            instanceFile.name(ins.name + ".html");
                            instanceFile.size(instanceHtml.length);

                            const metadata = {
                                owner: user_id,
                                owner_name: user_info.name,
                                space: space_id,
                                instance: new_ins_id,
                                approve: appr_obj._id,
                                current: true,
                            };
                            instanceFile.metadata = metadata;
                            const fileObj = await collection.insert(instanceFile);
                            await fileObj.update({
                                $set: { "metadata.parent": fileObj._id }
                            });
                        }
                    );
                } catch (e) {
                    console.error(e);
                }
            }

            if (isForwardAttachments && action_type == "forward") {
                await broker.call('instanceFiles.copyInstanceFiles', {
                    sourceInstanceId: instance_id, 
                    targetInstanceId: new_ins_id, 
                    targetInstanceApproveId: appr_obj._id, 
                    userSession, 
                    options: {
                        can_edit_main_attach,
                        can_edit_normal_attach
                    }
                })
            }

            if (action_type === "distribute") {
                const appr = {
                    _id: _makeNewID(),
                    instance: instance_id,
                    trace: current_trace_id,
                    is_finished: true,
                    user: user_id,
                    user_name: user_info.name,
                    handler: user_id,
                    handler_name: user_info.name,
                    handler_organization: space_user.organization,
                    handler_organization_name: space_user_org_info.name,
                    handler_organization_fullname: space_user_org_info.fullname,
                    type: action_type,
                    start_date: new Date(),
                    finish_date: new Date(),
                    is_read: false,
                    judge: "submitted",
                    from_user: current_user_id,
                    from_user_name: from_user_name,
                    forward_space: space_id,
                    forward_instance: new_ins_id,
                    description: description,
                    from_approve_id: from_approve_id,
                };

                forward_approves.push(appr);
            }

            new_ins_data.push({
                instanceId: new_ins_id,
                instanceTasksId: appr_obj._id
            });

            await pushManager.send_message_to_specifyUser("current_user", user_id);
        }

        if (!_.isEmpty(forward_approves)) {
            set_obj.modified = new Date();
            set_obj.modified_by = current_user_id;

            await instancesCollection.updateOne(
                { _id: instance_id, "traces._id": current_trace_id },
                {
                    $set: set_obj,
                    $addToSet: {
                        "traces.$.approves": { $each: forward_approves }
                    }
                }
            );
        }

        res.status(200).send({ data: new_ins_data });
    } catch (error) {
        console.error(error);
        res.status(200).send({ error: error.message });
    }
});

exports.default = router;