import Context from "../../context"
import { IImportConfig, ImportResponse, ReturnMessage, ImportResult, ImportMode, IUserImportRequest } from "../../models/import"
import { sequelize } from "../../models"
import { ModelsManager, ModelManager, AttrGroupWrapper, UserWrapper } from "../../models/manager"
import { User } from "../../models/users"
import bcrypt from 'bcryptjs';
import { Op, literal } from 'sequelize'

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

export async function importUser(context: Context, config: IImportConfig, user: IUserImportRequest): Promise<ImportResponse> {
    const result = new ImportResponse(user.login)

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

    try {
        const mng = ModelsManager.getInstance().getModelManager(context.getCurrentUser()!.tenantId)
        const idx = mng.getUsers().findIndex(elem => elem.getUser().login === user.login)
        if (user.delete) {
            if (idx === -1) {
                result.addError(ReturnMessage.UserNotFound)
                result.result = ImportResult.REJECTED
            } else {
                const data = mng.getUsers()[idx].getUser()
                const wrapper = mng.getUsers()[idx]
                const adminRole = wrapper.getRoles().find(role => role.identifier === 'admin')
                if (adminRole) {
                    // check that we has another user with admin role
                    // const tst:User = await User.findOne({where: {id: {[Op.ne]:data.id}, roles: {[Op.contains]: adminRole.id}}})
                    const tst = await User.findOne({where: {[Op.and]:[{id: {[Op.ne]:data.id}}, literal("roles @> '"+adminRole.id+"'")]}})
                    if (!tst) {
                        result.addError(ReturnMessage.UserDeleteFailed)
                        result.result = ImportResult.REJECTED
                        return result
                    }
                }
    
                data.updatedBy = context.getCurrentUser()!.login
                data.login = data.login + '_d_' + Date.now() 
                await sequelize.transaction(async (t) => {
                    await data.save({transaction: t})
                    await data.destroy({transaction: t})
                })
    
                mng.getUsers().splice(idx, 1)
                                
                result.result = ImportResult.DELETED
            }
            return result
        }

        if (config.mode === ImportMode.CREATE_ONLY) {
            if (idx !== -1) {
                result.addError(ReturnMessage.UserExist)
                result.result = ImportResult.REJECTED
                return result
            }
        } else if (config.mode === ImportMode.UPDATE_ONLY) {
            if (idx === -1) {
                result.addError(ReturnMessage.UserNotFound)
                result.result = ImportResult.REJECTED
                return result
            }
        }        

        if (idx === -1) {
            // create
            const tst = await User.findOne({where: {login: user.login}})
            if (tst) {
                result.addError(ReturnMessage.UserExist)
                result.result = ImportResult.REJECTED
                return result
            }

            let roleIds = checkRoles(user.roles, mng, result)
            if (result.result) return result

            const data = await sequelize.transaction(async (t) => {
                return await User.create({
                    tenantId: context.getCurrentUser()!.tenantId,
                    createdBy: context.getCurrentUser()!.login,
                    updatedBy: '',
                    login: user.login,
                    name: user.name,
                    password: await bcrypt.hash("password", 10),
                    email: user.email,
                    roles: roleIds,
                    props: user.props || {},
                    options: user.options ?  user.options : []
                  }, {transaction: t});
            })

            const userRoles = roleIds.map((roleId: number) => mng!.getRoles().find(role => role.id === roleId)!)
            mng.getUsers().push(new UserWrapper(data, userRoles));

            result.id = ""+data.id
            result.result = ImportResult.CREATED
        } else {
            // update
            const wrapper = mng.getUsers()[idx]
            const data = wrapper.getUser()

            if (user.name) data.name = user.name
            if (user.email) data.email = user.email
            if (user.props) data.props = user.props

            if (user.roles) {
                let roleIds = checkRoles(user.roles, mng, result)
                if (result.result) return result
                data.roles = roleIds

                const userRoles = roleIds.map((roleId: number) => mng!.getRoles().find(role => role.id === roleId)!)
                wrapper.setRoles(userRoles)
            }

            if (user.options != null) data.options = user.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 checkRoles(roles: [string], mng: ModelManager, result: ImportResponse) {
    let res: number[] = []
    if (roles) {
        for (let index = 0; index < roles.length; index++) {
            const roleIdentifier = roles[index];
            const tst = mng.getRoles().find(elem => elem.identifier === roleIdentifier)
            if (!tst) {
                result.addError(ReturnMessage.RoleNotFound)
                result.result = ImportResult.REJECTED
                return <number[]>[]
            }
            res.push(tst.id)
        }
    }
    return res
}