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 } = 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) => value.length < 50 && /^[#a-z0-9а-я-\s,.()]+$/i.test(value)
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 analyzeImage = async (fileId, token) => {
    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: "GigaChat-Max",
            stream: false,
            update_interval: 0,
            messages: [
                {
                    role: "system",
                    content:
                        `Ты эксперт по оценке степени загрязнения автомобилей. Твоя задача — анализировать фотографии машин и определять степень их загрязнения.
ВАЖНО: Твой ответ ДОЛЖЕН быть СТРОГО в формате JSON и содержать ТОЛЬКО следующие поля:
{
"value": число от 0 до 10 (целое или с одним знаком после запятой),
"description": "текстовое описание на русском языке"
}
Правила:
1. Поле "value":
- Должно быть числом от 0 до 10
- 0 = машина абсолютно чистая
- 10 = машина максимально грязная
2. Поле "description":
- Должно содержать 2-3 предложения на русском языке
- Обязательно указать конкретные признаки загрязнения
- Объяснить почему выставлен именно такой балл
НЕ ДОБАВЛЯЙ никаких дополнительных полей или комментариев вне JSON структуры.
НЕ ИСПОЛЬЗУЙ markdown форматирование.
ОТВЕТ ДОЛЖЕН БЫТЬ ВАЛИДНЫМ JSON.`,
                },
                {
                    role: "user",
                    content: 'Дай оценку для приложенного файла изображения согласно структуре, ответ должен быть на русском языке',
                    attachments: [fileId],
                },
            ],
        }),
    })

    const data = await response.json()
    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) ?? {}

        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