import Context from "../../context"
import { IImportConfig, IItemRelationImportRequest, ImportResponse, ReturnMessage, ImportResult, ImportMode, ErrorProcessing, ITypeImportRequest } from "../../models/import"
import { sequelize } from "../../models"
import { ModelsManager, TreeNode, ModelManager } from "../../models/manager"
import { Type } from "../../models/types"
import { QueryTypes } from "sequelize"
import e = require("express")
import { Relation } from "../../models/relations"
import { Attribute } from "../../models/attributes"
import { Item } from "../../models/items"
import { Op, literal } from 'sequelize'

import logger from '../../logger'

/*
mutation { import(
    config: {
        mode: CREATE_UPDATE
        errors: PROCESS_WARN
    },
    types: [
        {
            delete: false
            identifier: "tst",
            parentIdentifier: "",
            name: {ru: "test type2"}
            icon: "folder",
            iconColor: "red"
        }
    ]
    ) {
    types {
	  identifier
	  result
	  id
	  errors { code message }
	  warnings { code message }
    }}}
    
*/
export async function importType(context: Context, config: IImportConfig, type: ITypeImportRequest): Promise<ImportResponse> {
    const result = new ImportResponse(type.identifier)

    if (!type.identifier || !/^[A-Za-z0-9_-]*$/.test(type.identifier)) {
        result.addError(ReturnMessage.WrongIdentifier)
        result.result = ImportResult.REJECTED
        return result
    }

    try {
        const mng = ModelsManager.getInstance().getModelManager(context.getCurrentUser()!.tenantId)
        if (type.delete) {
            const typeNode = mng.getTypeByIdentifier(type.identifier)
            if (!typeNode) {
                result.addError(ReturnMessage.TypeNotFound)
                result.result = ImportResult.REJECTED
            } else {
                if (typeNode.getChildren().length > 0) {
                    result.addError(ReturnMessage.TypeDeleteFailed)
                    result.result = ImportResult.REJECTED
                } else {
                    const parentNode = typeNode.getParent()!
                    parentNode.deleteChild(typeNode)
        
                    const type:Type = typeNode.getValue()

                    const nId = type.id
                    // check Roles
                    const tst4 = mng.getRoles().find(role => role.itemAccess.valid.includes(nId))
                    // check Relations
                    //const tst3 = await Relation.applyScope(context).findOne({
                    //    where: {[Op.or]: [{sources: { [Op.contains]: nId}}, {targets: { [Op.contains]: nId}}]}
                    //})
                    const tst3 = await Relation.applyScope(context).findOne({
                        where: {[Op.or]: [literal("sources @> '"+nId+"'"), literal("targets @> '"+nId+"'")]}
                    })
                    // check Attributes
                    // const tst2 = await Attribute.applyScope(context).findOne({where: {valid: { [Op.contains]: nId}}})
                    const tst2 = await Attribute.applyScope(context).findOne({where: literal("valid @> '"+nId+"'")})
                    // check Items
                    const tst1 = await Item.applyScope(context).findOne({where: {typeId: nId}})
                    // check Linked types
                    const tst5 = mng.getTypeByLinkId(nId)

                    if (tst1 || tst2 || tst3 || tst4 || tst5) {
                        result.addError(ReturnMessage.TypeCanNotDelete)
                        result.result = ImportResult.REJECTED
                        return result
                    }

                    type.updatedBy = context.getCurrentUser()!.login
                    type.identifier = type.identifier + '_d_' + Date.now() 
                    await sequelize.transaction(async (t) => {
                        await type.save({transaction: t})
                        await type.destroy({transaction: t})
                    })
        
                    result.result = ImportResult.DELETED
                }
            }
            return result
        }

        const typeNode = mng.getTypeByIdentifier(type.identifier)
        if (config.mode === ImportMode.CREATE_ONLY) {
            if (typeNode) {
                result.addError(ReturnMessage.TypeExist)
                result.result = ImportResult.REJECTED
                return result
            }
        } else if (config.mode === ImportMode.UPDATE_ONLY) {
            if (!typeNode) {
                result.addError(ReturnMessage.TypeNotFound)
                result.result = ImportResult.REJECTED
                return result
            }
        }        

        if (!typeNode) {
            // create
            let parentNode: TreeNode<any> | null = null
            if (type.parentIdentifier) {
                parentNode = mng.getTypeByIdentifier(type.parentIdentifier)
                if (!parentNode) {
                    result.addError(ReturnMessage.TypeParentNotFound)
                    result.result = ImportResult.REJECTED
                    return result
                }
            }

            let link:Type
            if (type.linkIdentifier) {
                link = mng.getTypeByIdentifier(type.linkIdentifier)?.getValue()
                if (!link) {
                    result.addError(ReturnMessage.TypeLinkNotFound)
                    result.result = ImportResult.REJECTED
                    return result
                }
            }

            const results:any = await sequelize.query("SELECT nextval('types_id_seq')", { 
                type: QueryTypes.SELECT
            });
            const newId = (results[0]).nextval

            let path = '' + newId
            let parentId = null
            if (parentNode) {
                parentId = parentNode?.getValue().id
                while(parentNode != mng.getRoot()) {
                    path = parentNode!.getValue().id + '.' + path
                    parentNode = parentNode!.getParent()
                }
            }

            let images = checkRelations(type, mng, result)
            if (result.result) return result

            let mainImageId = 0
            if (type.mainImage) {
                const tst = mng.getRelationByIdentifier(type.mainImage)
                if (!tst) {
                    result.addWarning(ReturnMessage.RelationNotFound)
                } else {
                    mainImageId = tst.id;
                }
            }

            const data = await sequelize.transaction(async (t) => {
                return await Type.create ({
                    id: newId,
                    path: path,
                    identifier: type.identifier,
                    icon: type.icon,
                    iconColor: type.iconColor,
                    tenantId: context.getCurrentUser()!.tenantId,
                    createdBy: context.getCurrentUser()!.login,
                    updatedBy: context.getCurrentUser()!.login,
                    name: type.name || null,
                    link: link ? link.id : 0,
                    file: type.file != null ? type.file : false,
                    mainImage: mainImageId,
                    images: images,
                    options: type.options ?  type.options : []
                }, {transaction: t})
            })
            mng.addType(parentId, data)

            result.id = ""+data.id
            result.result = ImportResult.CREATED
        } else {
            // update
            if (type.parentIdentifier) {
                result.addWarning(ReturnMessage.TypeUpdateParent);
                if (config.errors == ErrorProcessing.WARN_REJECTED) {
                    result.result = ImportResult.REJECTED
                    return result
                }
            }

            if (type.linkIdentifier) {
                result.addWarning(ReturnMessage.TypeUpdateLink);
                if (config.errors == ErrorProcessing.WARN_REJECTED) {
                    result.result = ImportResult.REJECTED
                    return result
                }
            }

            const data: Type = typeNode.getValue()
            if (type.name) data.name = type.name
            if (type.icon) data.icon = type.icon
            if (type.iconColor) data.iconColor = type.iconColor
            if (type.file != null) data.file = type.file

            if (type.images) {
                data.images = checkRelations(type, mng, result)
                if (result.result) return result
            }

            if (type.mainImage) {
                const tst = mng.getRelationByIdentifier(type.mainImage)
                if (!tst) {
                    result.addWarning(ReturnMessage.RelationNotFound)
                } else {
                    data.mainImage = tst.id;
                }
            }
            if (type.options != null) data.options = type.options

            data.updatedBy = context.getCurrentUser()!.login
            await sequelize.transaction(async (t) => {
                await data.save({transaction: t})
            })

            result.id = ""+data.id
            result.result = ImportResult.UPDATED
        } 
    } catch (error) {
        result.addError(new ReturnMessage(0, ""+error))
        result.result = ImportResult.REJECTED
        logger.error(error)
    }

    return result
}

function checkRelations(type: ITypeImportRequest, mng: ModelManager, result: ImportResponse) {
    let rels: number[] = []
    if (type.images) {
        for (let index = 0; index < type.images.length; index++) {
            const relIdentifier = type.images[index];
            const tst = mng.getRelationByIdentifier(relIdentifier)
            if (!tst) {
                result.addWarning(ReturnMessage.RelationNotFound)
            } else {
                rels.push(tst.id)
            }
        }
    }
    return rels
}