
const path = require('path')
const getIntegrationsConfig = require('../utils/getIntegrationsConfig')

const { StatusCodes } = require("http-status-codes")
const { createCRMContact,
    sendMessageSocket,
    mustContainProperties,
    journaling,
    ticketCRM,
    findProperty,
    journalingRequest,
    createContact,
    lookupContactByPhone,
    templateValidator,
    loadCRM } = require('../utils')
const fs = require("fs")
const { URL } = require('url')
const { oauth2 } = require('../utils')
const { exchangeForTokens } = oauth2
const Company = require('../models/Company')
const CRM = require('../models/CRM')
const CustomError = require('../errors')
const CRM_Contact = require('../models/CRM_Contact')
const axios = require('axios')
const { get, set } = require('../utils/redisClient')
const { getContactIdChatwoot, createConversation, toggleConversationStatus } = require('../utils/ScheduleTicketCRM')
const timeStamp = require('../utils/toTimestamp')
const whatsappJournalingCRM = require('../utils/whatsappJournalingCRM')
const redirectContactLinkCRM = require('../utils/redirectContactLinkCRM')
const sfcase = require('../utils/sfCase')
const sfCaseUpdate = require('../utils/sfCaseUpdate')
const removeZeroInicial = require('../utils/removeZeroInicial')
const getSalesforceUser = require('../utils/getSalesforceUser')


const contactCreate = async (req, res) => {

    const { companyId, crmFirstName, crmLastName, crmPhone, crmEmail, dynamicBodyRequest } = req.body

    mustContainProperties(req, ['companyId', 'crmPhone',])
 
    await createCRMContact(companyId, crmFirstName, crmPhone, crmEmail, crmLastName, dynamicBodyRequest)

    res.status(StatusCodes.OK).send()

}

const checkContact = async (req, res) => {

    const { companyId, crmPhone } = req.body

    mustContainProperties(req, ['companyId', 'crmPhone',])

    const crmFiles = await loadCRM(companyId)

    if (crmFiles.length > 0) {
        const { crmRest: rest, authentication } = crmFiles[0].crm

        const contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId)

        if (contact && contact.exist) {
            return res.status(StatusCodes.OK).json({ exist: contact.exist })
        }
    }

    res.status(StatusCodes.OK).json({ exist: false })
}

const contactActivity = async (req, res) => {
    const { companyId, crmPhone, ticketId } = req.body

    mustContainProperties(req, ['companyId', 'crmPhone', 'ticketId'])
    
    await whatsappJournalingCRM(
        companyId,
        crmPhone,
        '0000',
        crmFirstName = "unnamed",
        ticketId
    )

    res.status(StatusCodes.OK).send()
}

const deleteCrm = async (req, res) => {

    let { companyId, crmBaseURL } = req.body

    mustContainProperties(req, ['companyId', 'crmBaseURL',])

    if (!crmBaseURL.startsWith('http://') && !crmBaseURL.startsWith('https://')) {
        crmBaseURL = `https://${crmBaseURL.trim()}`
    }

    const crm = await CRM.findOne({
        companyId, crmBaseURL: new URL(crmBaseURL.trim()).hostname
    })

    if (crm) await crm.deleteOne()

    res.status(StatusCodes.OK).send()
}

const deleteCompany = async (req, res) => {

    const { companyId } = req.body

    mustContainProperties(req, ['companyId',])

    const company = await Company.findOne({ companyId, })

    if (company) await company.deleteOne()

    res.status(StatusCodes.OK).send()
}

const callJournaling = async (req, res) => {

    try {
        let { companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName, operationStatus } = req.body

        console.log(`[REQ.BODY - ${new Date()}] callJournaling: `, JSON.stringify(req.body, null, 6))


        // Remove 0 from the beginning of the number. Ex: 011996067641 to 11996067641
        crmPhone = removeZeroInicial(crmPhone)



        // test remove
        // return res.status(StatusCodes.OK).send()

        // Refactor this in the future.
        crmPhone = '55' + crmPhone
        console.log('========> CRMPHONE: ', crmPhone)
        console.log('========> COMPANY ID before: ', companyId)


        if (companyId == "1")
            companyId = '99'


        console.log('========> COMPANY ID after: ', companyId)


        if (!crmAgent)
            crmAgent = "0000"
        //


        mustContainProperties(req, ['companyId', 'operation', 'crmPhone', /*'crmAgent'*/,])


        // if (operation == 'inboundAnsweredCall' && !crmCallDuration)
        //     throw new CustomError.BadRequestError(`The crmCallDuration property must be provided when operation is inboundAnsweredCall`)
        // if (operation == 'outboundAsweredCall' && !crmCallDuration)
        //     throw new CustomError.BadRequestError(`The crmCallDuration property must be provided when operation is outboundAsweredCall`)




        if (!crmCallDuration || crmCallDuration.trim() == "" || crmCallDuration == "0")
            crmCallDuration = "10"




        if (operationStatus == "hangup")
            await journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName)


        else if (operationStatus == "update-answer") {
            console.log('update-answer')

            await ticketCRM(companyId, crmPhone, crmAgent, crmFirstName)

            const resp = await redirectContactLinkCRM(companyId, crmPhone, crmAgent, crmFirstName)
            return res.status(StatusCodes.OK).json({ contact: resp })
        }


        res.status(StatusCodes.OK).send()
    } catch (error) {
        // console.log(`[ERROR - ${new Date()}] Erro no Call Journaling`, error?.response?.data)

        if (error.response) {
            console.error('==================> callJournaling Erro na resposta da API:', {
                status: error.response.status,
                data: error.response.data,
            })
        }
        else if (error.request) {
            console.error('==================> callJournaling Nenhuma resposta recebida da API:', error.request)
        }
        else {
            console.error('==================> callJournaling Erro ao configurar a request:', error.message)
        }

        res.status(StatusCodes.INTERNAL_SERVER_ERROR).send()
    }


}


const sfCreateCase = async (req, res) => {

    const { companyId, crmPhone } = req.body

    mustContainProperties(req, ['companyId', 'crmPhone',])

    const sfCaseRes = await sfcase(companyId, crmPhone)

    res.status(StatusCodes.OK).json({ ...sfCaseRes })
}

const sfUpdateCase = async (req, res) => {

    const { companyId, caseId, agentEmail, case: caseUpdate } = req.body

    mustContainProperties(req, ['companyId', 'caseId', 'case'])

    const resCaseUpdate = await sfCaseUpdate(companyId, caseId, caseUpdate, agentEmail)

    if (resCaseUpdate) res.status(StatusCodes.OK).send()

    if (!resCaseUpdate) res.status(StatusCodes.UNPROCESSABLE_ENTITY).send()

}

const install = async (req, res) => {

    const { authUrl, companyId } = req.query

    console.log('--------> authUrl: ', authUrl)

    // Store the authUrl in the session
    req.session.authUrl = authUrl + `&companyId=${companyId}`

    res.redirect(authUrl)
}

const oauthCallBack = async (req, res) => {

    const { code } = req.query

    // Retrieve the stored authUrl from the session
    const storedAuthUrl = req.session.authUrl

    console.log('xxxxxxxxxx storedAuthUrl: ', storedAuthUrl)

    const parsedUrl = new URL(storedAuthUrl)
    const clientId = parsedUrl.searchParams.get('client_id')
    const companyId = parsedUrl.searchParams.get('companyId')

    console.log('xxxxxxxxxx clientId: ', clientId)
    console.log('xxxxxxxxxx companyId: ', companyId)
    console.log('xxxxxxxxxx code: ', code)

    if (code) {

        console.log('xxxxxxxxxx passed')

        let crmOauth = await CRM.findOne({ 'crm.authentication.crmClientId': clientId, 'companyId': companyId })

        crmOauth = crmOauth.toObject()

        let authCodeProof = {
            grant_type: 'authorization_code',
            client_id: crmOauth.crm.authentication.crmClientId,
            client_secret: crmOauth.crm.authentication.crmClientSecret,
            redirect_uri: process.env.URL_OAUTH_CALLBACK,
            code
        }

        //The PKCE is mandatory for salesforce. This is for salesforce only oauth2 that need a code_challenge and code_verifier.  
        const rest = crmOauth.crm.crmRest
        const salesforceUrls = rest.map(endpoint => {
            const key = Object.keys(endpoint)[0]
            const url = endpoint[key]?.request?.url

            if (url?.includes("salesforce")) {
                return `${key} URL: ${url}`
            }
        }).filter(Boolean)

        if (salesforceUrls.find(url => url.includes('salesforce'))) {
            authCodeProof = {
                ...authCodeProof, ...{
                    code_verifier: `gwkPueV2GSzkFvGFiHbNjpRuq_XBEGBsihM59pMaiFalZrOQ_7J4hgtBR8GIbLHutcuUwba2k0xeKhD8ELjWEswE3xv-H2e6Jh8EOwOccj2i_rYPUlMGdPDqpuRs8D3w`,
                    code: `${code.split('%')[0]}`
                }
            }
        }
        //



        // Exchange the authorization code for an access token and refresh token 
        await exchangeForTokens(crmOauth, authCodeProof, companyId)

        const url = `${process.env.URL_OAUTH_FRONTEND_SUCCESS_REDIRECT}?clientId=${encodeURIComponent(clientId)}&codWeb=${companyId}`

        return res.redirect(url)
        // return res.redirect(process.env.URL_OAUTH_FRONTEND_SUCCESS_REDIRECT)
    }

    res.status(StatusCodes.OK).send()
}

const testTemplate = async (req, res) => {

    let { clientId, companyId } = req.body

    let crmOauth = await CRM.findOne({ 'crm.authentication.crmClientId': clientId, 'companyId': companyId, testing: true })

    if (!crmOauth)
        return res.status(StatusCodes.OK).send()

    crmOauth = crmOauth.toObject()

    const { crmPhoneTest } = crmOauth.crm.authentication

    await templateValidator(crmPhoneTest, crmOauth.crm, companyId)

    crmOauth = await CRM.findOne({ 'crm.authentication.crmClientId': clientId, 'companyId': companyId })
    crmOauth.testing = false
    await crmOauth.save()

    res.status(StatusCodes.OK).send()

}

const uploadCrmConfig = async (req, res) => {

    const { companyId, } = req.body

    mustContainProperties(req, ['companyId',])

    //test
    // console.log('============> uploadCrmConfig companyId: ', companyId)
    // return res.send()
    //

    if (!req?.file)
        throw new CustomError.BadRequestError(`The crm property file must be provided`)

    const file = req.file

    let newCrm = fs.readFileSync(file.path, "utf8")

    newCrm = JSON.parse(newCrm)

    // console.log('===========> NEW CRM: ', JSON.stringify(newCrm, null, 6))

    const index = newCrm.crmRest.findIndex(rest => rest?.createContactRecord)

    const crmBaseURL = new URL(newCrm.crmRest[index].createContactRecord.request.url.trim()).hostname

    let company = await Company.findOne({ companyId })

    if (!company) {
        company = await Company.create({ companyId })
    }

    let crm = await CRM.findOne({ companyId, crmBaseURL })

    if (crm) {
        crm.testing = true
        crm.crm = newCrm
        await crm.save()
    }
    else {
        crm = await CRM.create({
            crmBaseURL,
            companyId: company.companyId,
            company,
            testing: true,
            crm: newCrm
        })
    }

    fs.unlinkSync(file.path)

    const { type, crmClientId, crmClientSecret, crmScopes, crmPhoneTest } = crm.crm.authentication

    if (type == 'oauth2') {

        const { request, body, response } = findProperty(crm.crm.crmRest, 'authorizationEndpoint')

        let { url } = request

        url = url.replace('crmClientId', crmClientId)
            .replace('crmClientSecret', crmClientSecret)
            .replace('crmScopes', crmScopes)
            .replace('crmRedirectURI', process.env.URL_OAUTH_CALLBACK)


        const authUrl = `https://api-integracao-crm.hitmanager.app.br/api/v1/crm/install?authUrl=${encodeURIComponent(url)}&companyId=${encodeURIComponent(companyId)}`
        console.log('--------> authUrl: ', authUrl)

        return res.status(StatusCodes.OK).json({ url: authUrl })

    }
    else {

        await templateValidator(crmPhoneTest, newCrm, companyId)

        crm.testing = false
        await crm.save()
    }

    res.status(StatusCodes.OK).send()
}


const getCrms = async (req, res) => {
    const { companyId, } = req.params

    if (!companyId)
        throw new CustomError.BadRequestError(`The companyId must be provided in the URL`)

    const crms = await CRM.find({ companyId }, { _id: 1, crmBaseURL: 1, createdAt: 1, companyId: 1, enabled: 1 })

    res.status(StatusCodes.OK).send(crms)
}

const createTicket = async (req, res) => {

    let { companyId, crmPhone } = req.body

    mustContainProperties(req, ['companyId', 'crmPhone'])

    const crmTicketLinks = await ticketCRM(companyId, crmPhone)
        .catch(function (error) {

            console.error(`Error on create ticket: companyID ${companyId} | crmPhone: ${crmPhone}`)
            console.error(error?.response?.data)
            console.error(error?.response?.status)
            console.error(error?.response?.headers)

            throw new Error(`Error on create ticket: companyID ${companyId} | crmPhone: ${crmPhone}`)
        })

    return res.status(StatusCodes.OK).json({ crmTicketLinks })
}


const webhook = async (req, res) => {

    const originIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress
    console.log('========> Origem da requisição IP: ', originIP)

    console.log('========> webhook req.body: ', JSON.stringify(req.body, null, 6))

    console.log('========> req.body: ', req.body)

    if (!req.body?.meta)
        return res.status(StatusCodes.OK).send()

    let { name, phone_number } = req.body.meta.sender

    const { id: ticketId, account_id: accountId } = req.body

    //channel: 'Channel::FacebookPage'
    console.log("2 =============> ticketId id: ", ticketId, " | accountId account_id: ", accountId)

    if (!phone_number) {

        console.log("=============> Ignored process phone_number: ", phone_number, " | 'Channel::FacebookPage'")

        return res.send()
    }

    let crmPhone = phone_number.replace('+', '')

    const company = await Company.findOne({
        integrations: {
            $elemMatch: {
                name: "omnihit",
                "config.accountId": `${accountId}`
            }
        }
    })

    if (!company) {
        return res.status(StatusCodes.NOT_FOUND).send({ msg: "companyId not found!" })
    }

    console.log('=======> name: ', name)
    console.log('=======> crmPhone: ', crmPhone)
    console.log('=======> accountId: ', accountId)
    console.log('=======> companyId: ', company.companyId)

    console.log('=======> ticketId: ', ticketId)


    await whatsappJournalingCRM(
        companyId = company.companyId,
        crmPhone = crmPhone,
        crmAgent = '0000',
        crmFirstName = name,
        ticketId
    )

    res.status(StatusCodes.OK).send()
}

const webhook_crm = async (req, res) => {

    const originIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress
    console.log('========> Crm Origem da requisição IP: ', originIP)

    console.log('========> webhook crm req.body: ', JSON.stringify(req.body, null, 6))

    const responseXml = `
                <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
                    <soapenv:Header/>
                    <soapenv:Body>
                        <notificationsResponse xmlns="urn:enterprise.soap.sforce.com">
                            <Ack>true</Ack>
                        </notificationsResponse>
                    </soapenv:Body>
                </soapenv:Envelope>`

    const sObject = req.body['soapenv:Envelope']['soapenv:Body']?.notifications?.Notification?.sObject

    if (!sObject) {
        return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml)
    }

    const url = req.body['soapenv:Envelope']['soapenv:Body']?.notifications?.EnterpriseUrl

    console.log('========> salesforce url: ', url)

    const crm = await CRM.findOne({ crmBaseURL: new URL(url.trim()).hostname })

    const { companyId } = crm

    console.log('========> crm companyId: ', companyId)

    const whoId = sObject['sf:WhoId']
    const EventSubtype = sObject['sf:EventSubtype']
    const StartDateTime = sObject['sf:StartDateTime']

    console.log('==========> crm EventSubtype: ', EventSubtype)
    console.log('==========> crm StartDateTime: ', StartDateTime)
    console.log('==========> crm StartDateTime timeStamp: ', timeStamp(StartDateTime))

    if (EventSubtype == 'Event') {

        const contact = await CRM_Contact.findOne({
            companyId,
            crmBaseURL: new URL(url.trim()).hostname,
            contactId: whoId
        })

        console.log('==========> crm whoId: ', whoId)
        console.log('==========> crm contact: ', contact)

        if (!contact) return res.send()

        const { phone } = contact

        const config = await getIntegrationsConfig(companyId, 'omnihit')

        if (config) {

            const { accountId, api: { url, token } = {},
                createConversation: { inbox_id, status, team_id } = {} = {} } = config

            const contactIdChatwoot = await getContactIdChatwoot(url, token, phone)

            if (!contactIdChatwoot) {
                return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml)
            }

            console.log('==========> crm contactIdChatwoot: ', contactIdChatwoot)

            let data = { inbox_id, contact_id: contactIdChatwoot, status, team_id }
            const omnihitConfig = { url, accountId, token }
            const ticketId = await createConversation(data, omnihitConfig)

            console.log('==========> crm ticketId: ', ticketId)

            data = { "status": "snoozed", "snoozed_until": timeStamp(StartDateTime) }

            await toggleConversationStatus(url, accountId, token, ticketId, data)
        }
    }

    return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml)
}

module.exports = {
    contactCreate,
    uploadCrmConfig,
    callJournaling,
    oauthCallBack,
    install,
    deleteCrm,
    deleteCompany,
    testTemplate,
    getCrms,
    webhook,
    webhook_crm,
    sfCreateCase,
    sfUpdateCase,
    createTicket,
    checkContact,
    contactActivity
}



