const mongoose = require("mongoose")
const router = require('express').Router()
const multer = require('multer')
const { MasterModel } = require('./model/master')
const { OrderModel } = require('./model/order')
const { OrderCarImgModel } = require('./model/order.car-img')
const { orderStatus } = require('./model/const')
const { getGigaToken, getSystemPrompt, getGigaChatModel } = require('./get-token')

const isValidPhoneNumber = (value) => /^(\+)?\d{9,15}/.test(value)
const isValidCarNumber = (value) => /^[авекмнорстух][0-9]{3}[авекмнорстух]{2}[0-9]{2,3}$/i.test(value)
const isValidCarBodyType = (value) => typeof value === 'number' && value > 0 && value < 100
const isValidCarColor = (value) => {
    if (typeof value === 'number') {
        return value >= 0 && value <= 7
    } else if (typeof value === 'string') {
        return /^[#a-z0-9а-я-\s,.()]+$/i.test(value)
    }
    return false
}
const isValidISODate = (value) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d{1,3})?Z$/.test(value)

const latitudeRe = /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/
const longitudeRe = /^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,18})?|180(?:\.0{1,18})?)$/
const addressRe = /^[а-я0-9\s,.'-/()]*$/i
const isValidLocation = (value) => {
    if (value.length > 200) {
        return false
    }

    const [coordinates, address] = value.split(' ')
    const [latitude, longitude] = coordinates.split(',')
    return latitudeRe.test(latitude) && longitudeRe.test(longitude) && addressRe.test(address)
}

const isValidOrderStatus = (value) => Object.values(orderStatus).includes(value)
const isValidOrderNotes = (value) => value.length < 500

const allowedMimeTypes = ['image/jpeg', 'image/png']
const sizeLimitInMegaBytes = 15

const VALIDATION_MESSAGES = {
    order: {
        notFound: 'Order not found'
    },
    orderId: {
        invalid: 'Valid order ID is required',
    },
    orderStatus: {
        invalid: 'Invalid order status'
    },
    orderNotes: {
        invalid: 'Invalid order notes'
    },
    master: {
        notFound: 'Master not found'
    },
    masterId: {
        invalid: 'Invalid master ID',
    },
    phoneNumber: {
        required: 'Phone number is required',
        invalid: 'Invalid phone number'
    },
    carNumber: {
        required: 'Car number is required',
        invalid: 'Invalid car number'
    },
    carBody: {
        required: 'Car body type is required',
        invalid: 'Invalid car body type'
    },
    carColor: {
        invalid: 'Invalid car color'
    },
    carImg: {
        required: 'Car image file is required',
        invalid: {
            type: `Invalid car image file type. Allowed types: ${allowedMimeTypes}`,
            size: `Invalid car image file size. Limit is ${sizeLimitInMegaBytes}MB`
        }
    },
    washingBegin: {
        required: 'Begin time of washing is required',
        invalid: 'Invalid begin time of washing'
    },
    washingEnd: {
        required: 'End time of washing is required',
        invalid: 'Invalid end time of washing'
    },
    washingLocation: {
        required: 'Location of washing is required',
        invalid: 'Invalid location of washing'
    },
}

router.post('/create', async (req, res, next) => {
    const bodyErrors = []

    const { customer } = req.body
    if (!customer.phone) {
        bodyErrors.push(VALIDATION_MESSAGES.phoneNumber.required)
    } else if (!isValidPhoneNumber(customer.phone)) {
        bodyErrors.push(VALIDATION_MESSAGES.phoneNumber.invalid)
    }

    const { car } = req.body
    if (!car.number) {
        bodyErrors.push(VALIDATION_MESSAGES.carNumber.required)
    } else if (!isValidCarNumber(car.number)) {
        bodyErrors.push(VALIDATION_MESSAGES.carNumber.invalid)
    }
    if (!car.body) {
        bodyErrors.push(VALIDATION_MESSAGES.carBody.required)
    } else if (!isValidCarBodyType(car.body)) {
        bodyErrors.push(VALIDATION_MESSAGES.carBody.invalid)
    }
    if (!isValidCarColor(car.color)) {
        bodyErrors.push(VALIDATION_MESSAGES.carColor.invalid)
    }

    const { washing } = req.body
    if (!washing.begin) {
        bodyErrors.push(VALIDATION_MESSAGES.washingBegin.required)
    } else if (!isValidISODate(washing.begin)) {
        bodyErrors.push(VALIDATION_MESSAGES.washingBegin.invalid)
    }
    if (!washing.end) {
        bodyErrors.push(VALIDATION_MESSAGES.washingEnd.required)
    } else if (!isValidISODate(washing.end)) {
        bodyErrors.push(VALIDATION_MESSAGES.washingEnd.invalid)
    }
    if (!washing.location) {
        bodyErrors.push(VALIDATION_MESSAGES.washingLocation.required)
    } else if (!isValidLocation(washing.location)) {
        bodyErrors.push(VALIDATION_MESSAGES.washingLocation.invalid)
    }

    if (bodyErrors.length > 0) {
        throw new Error(bodyErrors.join(', '))
    }

    try {
        const order = await OrderModel.create({
            phone: customer.phone,
            carNumber: car.number,
            carBody: car.body,
            carColor: car.color,
            startWashTime: washing.begin,
            endWashTime: washing.end,
            location: washing.location,
            status: orderStatus.PROGRESS,
            notes: '',
            created: new Date().toISOString(),
        })

        res.status(200).send({ success: true, body: order })

    } catch (error) {
        next(error)
    }
})

router.get('/:id', async (req, res, next) => {
    const { id } = req.params

    if (!mongoose.Types.ObjectId.isValid(id)) {
        throw new Error(VALIDATION_MESSAGES.orderId.invalid)
    }

    try {
        const order = await OrderModel.findById(id)

        if (!order) {
            throw new Error(VALIDATION_MESSAGES.order.notFound)
        }

        const imgProps = await OrderCarImgModel.findOne({ orderId: order.id })

        res.status(200).send({ success: true, body: { ...order.toObject(), ...imgProps?.toObject() } })
    } catch (error) {
        next(error)
    }
})

router.patch('/:id', async (req, res, next) => {
    const { id } = req.params
    if (!mongoose.Types.ObjectId.isValid(id)) {
        throw new Error(VALIDATION_MESSAGES.orderId.invalid)
    }

    const bodyErrors = []

    const { status } = req.body
    if (status) {
        if (!isValidOrderStatus(status)) {
            bodyErrors.push(VALIDATION_MESSAGES.orderStatus.invalid)
        }
    }

    const { master: masterId } = req.body
    if (masterId) {
        if (!mongoose.Types.ObjectId.isValid(masterId)) {
            bodyErrors.push(VALIDATION_MESSAGES.masterId.invalid)
        } else {
            try {
                const master = await MasterModel.findById(masterId)
                if (!master) {
                    bodyErrors.push(VALIDATION_MESSAGES.master.notFound)
                }
            } catch (error) {
                next(error)
            }
        }
    }

    const { notes } = req.body
    if (notes) {
        if (!isValidOrderNotes(notes)) {
            bodyErrors.push(VALIDATION_MESSAGES.orderNotes.invalid)
        }
    }

    if (bodyErrors.length > 0) {
        throw new Error(bodyErrors.join(', '))
    }

    try {
        const updateData = {}
        if (status) {
            updateData.status = status
        }
        if (masterId) {
            updateData.master = masterId
        }
        if (notes) {
            updateData.notes = notes
        }
        updateData.updated = new Date().toISOString()

        const order = await OrderModel.findByIdAndUpdate(
            id,
            updateData,
            { new: true }
        )
        if (!order) {
            throw new Error(VALIDATION_MESSAGES.order.notFound)
        }

        res.status(200).send({ success: true, body: order })
    } catch (error) {
        next(error)
    }
})

router.delete('/:id', async (req, res, next) => {
    const { id } = req.params
    if (!mongoose.Types.ObjectId.isValid(id)) {
        throw new Error(VALIDATION_MESSAGES.orderId.invalid)
    }

    try {
        const order = await OrderModel.findByIdAndDelete(id, {
            new: true,
        })
        if (!order) {
            throw new Error(VALIDATION_MESSAGES.order.notFound)
        }
        res.status(200).send({ success: true, body: order })
    } catch (error) {
        next(error)
    }
})

const storage = multer.memoryStorage()
const upload = multer({
    storage: storage,
    limits: { fileSize: sizeLimitInMegaBytes * 1024 * 1024 },
    fileFilter: (req, file, cb) => {
        if (allowedMimeTypes.includes(file.mimetype)) {
            cb(null, true)
        } else {
            cb(new Error(VALIDATION_MESSAGES.carImg.invalid.type), false)
        }
    }
})

const { v4: uuidv4 } = require("uuid")
const axios = require('axios')

const GIGA_CHAT_OAUTH = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth'
const GIGA_CHAT_API = 'https://gigachat.devices.sberbank.ru/api/v1'

const getToken = async (req, res) => {
    const gigaToken = await getGigaToken()

    const rqUID = uuidv4()
    const body = new URLSearchParams({
        scope: "GIGACHAT_API_PERS",
    })

    const response = await fetch(GIGA_CHAT_OAUTH, {
        method: "POST",
        headers: {
            Authorization: `Basic ${gigaToken}`,
            "Content-Type": "application/x-www-form-urlencoded",
            Accept: "application/json",
            RqUID: rqUID,
        },
        body,
    })

    if (!response.ok) {
        const errorData = await response.json()
        console.error("Ошибка запроса: ", errorData)
        return res.status(response.status).json(errorData)
    }

    return await response.json()
}

const uploadImage = async (file, accessToken) => {
    const formData = new FormData()
    const blob = new Blob([file.buffer], { type: file.mimetype })
    formData.append('file', blob, file.originalname)
    formData.append('purpose', 'general')

    const config = {
        maxBodyLength: Infinity,
        headers: {
            'Content-Type': 'multipart/form-data',
            'Accept': 'application/json',
            'Authorization': `Bearer ${accessToken}`
        }
    }

    try {
        const response = await axios.post(`${GIGA_CHAT_API}/files`, formData, config)
        return response.data.id
    } catch (error) {
        console.error(error)
    }
}

const COLORS_MAP = ['white', 'black', 'silver', 'gray', 'beige-brown', 'red', 'blue', 'green']

const getColorName = (colorKey) => { 
    if (typeof colorKey === 'number' && COLORS_MAP[colorKey]) {
        return COLORS_MAP[colorKey]
    }
    return colorKey
}

const analyzeImage = async (fileId, token, imgProps) => {
    const response = await fetch(`${GIGA_CHAT_API}/chat/completions`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
            Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
            model: (await getGigaChatModel()) ?? "GigaChat-Max",
            stream: false,
            update_interval: 0,
            messages: [
                {
                    role: "system",
                    content: (await getSystemPrompt()) ?? `Ты эксперт по оценке степени загрязнения автомобилей. Твоя задача — анализировать фотографии машин и определять степень их загрязнения.
Тебе предоставляют фотографию, где явно выделяется одна машина (например, она расположена в центре и имеет больший размер в кадре по сравнению с остальными). 
ВАЖНО: Твой ответ ДОЛЖЕН быть СТРОГО в формате JSON и содержать ТОЛЬКО следующие поля:
{
 "value": число от 0 до 10 (целое или с одним знаком после запятой),
"description": "текстовое описание на русском языке" 
}.
Правила:
1. Поле "value":
- Должно быть числом от 0 до 10 (0 = машина абсолютно чистая, 10 = машина максимально грязная) ИЛИ undefined (если не удалось оценить);
2. Поле "description":
- Должно содержать 2-3 предложения на русском языке;
- Обязательно указать конкретные признаки загрязнения;
- Объяснить, почему выставлен именно такой балл.
- Должно быть связано только с автомобилем.
НЕ ДОБАВЛЯЙ никаких дополнительных полей или комментариев вне JSON структуры. НЕ ИСПОЛЬЗУЙ markdown форматирование. ОТВЕТ ДОЛЖЕН БЫТЬ ВАЛИДНЫМ JSON. Если на фотографии нельзя явно выделить одну машину, то ОЦЕНКА ДОЛЖНА ИМЕТЬ ЗНАЧЕНИЕ undefined и в описании должно быть указано, что по фотографии не удалось оценить степень загрязнения автомобиля, при этом НЕ ОПИСЫВАЙ НИЧЕГО ДРУГОГО КРОМЕ АВТОМОБИЛЯ`,
                },
                {
                    role: "user",
                    content: `Дай оценку для приложенного файла изображения согласно структуре, ответ должен быть на русском языке. Учти, что владелец указал, что исходный цвет машины: ${getColorName(imgProps.color)}`,
                    attachments: [fileId],
                },
            ],
        }),
    })

    const data = await response.json()
    console.log(data)

    try {
        return JSON.parse(data.choices[0].message.content)
    } catch (error) {
        console.error(error)
        return { description: data.choices[0].message.content }
    }
}

const convertFileToBase64 = (file) => {
    const base64Image = file.buffer.toString('base64')
    return `data:${file.mimetype};base64,${base64Image}`
}

process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"

router.post('/:id/upload-car-img', upload.single('file'), async (req, res) => {
    const { id: orderId } = req.params
    if (!mongoose.Types.ObjectId.isValid(orderId)) {
        throw new Error(VALIDATION_MESSAGES.orderId.invalid)
    }
    const order = await OrderModel.findById(orderId)
    if (!order) {
        throw new Error(VALIDATION_MESSAGES.order.notFound)
    }

    if (!req.file) {
        throw new Error(VALIDATION_MESSAGES.carImg.required)
    }

    try {
        await OrderCarImgModel.deleteMany({ orderId })

        const { access_token } = await getToken(req, res)

        const fileId = await uploadImage(req.file, access_token)
        const { value, description } = await analyzeImage(fileId, access_token, { carColor: order.carColor }) ?? {}

        const orderCarImg = await OrderCarImgModel.create({
            image: convertFileToBase64(req.file),
            imageRating: value,
            imageDescription: description,
            orderId: order.id,
            created: new Date().toISOString(),
        })

        res.status(200).send({ success: true, body: orderCarImg })
    } catch (error) {
        console.error(error)
    }
})

router.use((err, req, res, next) => {
    if (err instanceof multer.MulterError) {
        switch (err.message) {
            case 'File too large':
                return res.status(400).json({ success: false, error: VALIDATION_MESSAGES.carImg.invalid.size })
            default:
                return res.status(400).json({ success: false, error: err.message })
        }
    }

    throw new Error(err.message)
})

module.exports = router
