import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
import Dataset from '#models/dataset';
import Field from '#app/Library/Field';
import BaseModel from '#models/base_model';
import { DateTime } from 'luxon';
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import vine from '@vinejs/vine';
import mail from '@adonisjs/mail/services/main';
import logger from '@adonisjs/core/services/logger';
import { validate } from 'deep-email-validator';

interface Dictionary {
    [index: string]: string;
}

export default class DatasetsController {
    public async index({ auth, request, inertia }: HttpContext) {
        const user = (await User.find(auth.user?.id)) as User;
        const page = request.input('page', 1);
        let datasets: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();

        // if (request.input('search')) {
        //     // users = users.whereRaw('name like %?%', [request.input('search')])
        //     const searchTerm = request.input('search');
        //     datasets.where('name', 'ilike', `%${searchTerm}%`);
        // }

        if (request.input('sort')) {
            type SortOrder = 'asc' | 'desc' | undefined;
            let attribute = request.input('sort');
            let sortOrder: SortOrder = 'asc';

            if (attribute.substr(0, 1) === '-') {
                sortOrder = 'desc';
                // attribute = substr(attribute, 1);
                attribute = attribute.substr(1);
            }
            datasets.orderBy(attribute, sortOrder);
        } else {
            // users.orderBy('created_at', 'desc');
            datasets.orderBy('id', 'asc');
        }

        // const users = await User.query().orderBy('login').paginate(page, limit);
        const myDatasets = await datasets
            .where('server_state', 'approved')
            .where('reviewer_id', user.id)

            .preload('titles')
            .preload('user', (query) => query.select('id', 'login'))
            .preload('editor', (query) => query.select('id', 'login'))
            .paginate(page, 10);

        return inertia.render('Reviewer/Dataset/Index', {
            datasets: myDatasets.serialize(),
            filters: request.all(),
            can: {
                review: await auth.user?.can(['dataset-review']),
                reject: await auth.user?.can(['dataset-review-reject']),
            },
        });
    }

    public async review({ request, inertia, response }: HttpContext) {
        const id = request.param('id');
        const dataset = await Dataset.query()
            .where('id', id)
            // .preload('titles')
            // .preload('descriptions')
            .preload('user', (builder) => {
                builder.select('id', 'login');
            })
            .firstOrFail();

        const validStates = ['approved'];
        if (!validStates.includes(dataset.server_state)) {
            // session.flash('errors', 'Invalid server state!');
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('reviewer.dataset.list');
        }

        const fieldnames: Array<string> = await dataset.describe();
        const fields: Dictionary = {};
        for (const fieldName of fieldnames) {
            const field: Field = dataset.getField(fieldName) as Field;
            const modelClass = field.getValueModelClass();
            let fieldValues = field.getValue();
            let value = '';

            if (fieldValues === null || fieldValues == undefined) {
                continue;
            }

            if (modelClass === null) {
                if (typeof fieldValues === 'number') {
                    // If the field values are a number, use them as is
                    value = fieldValues.toString();
                } else {
                    // If the field values are not a number, use the replace() function to remove non-printable characters
                    value = fieldValues.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');
                }
            } else {
                if (!Array.isArray(fieldValues)) {
                    fieldValues = [fieldValues];
                }

                for (const fieldValue of fieldValues) {
                    if (fieldValue === null) {
                        continue;
                    }
                    if (modelClass.prototype instanceof BaseModel) {
                        // this.mapModelAttributes(fieldValue, childNode);
                        value = '<ul>';
                        Object.keys(fieldValue).forEach((prop) => {
                            let modelValue = fieldValue[prop];
                            // console.log(`${prop}: ${value}`);
                            if (modelValue != null) {
                                if (modelValue instanceof DateTime) {
                                    modelValue = modelValue.toFormat('yyyy-MM-dd HH:mm:ss').trim();
                                } else {
                                    modelValue = modelValue.toString().trim();
                                }

                                // Replace invalid XML-1.0-Characters by UTF-8 replacement character.
                                modelValue = modelValue.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');

                                value = value + '<li>' + prop + ' : ' + modelValue + '</li>';
                            }
                        });
                        value = value + '</ul>';
                    } else if (modelClass instanceof DateTime) {
                        // console.log('Value is a luxon date');
                        // this.mapDateAttributes(fieldValue, childNode);
                        value = value + ' Year ' + modelClass.year.toString();
                        value = value + ' Month ' + modelClass.month.toString();
                        value = value + ' Day ' + modelClass.day.toString();
                        value = value + ' Hour ' + modelClass.hour.toString();
                        value = value + ' Minute ' + modelClass.minute.toString();
                        value = value + ' Second ' + modelClass.second.toString();
                        value = value + ' UnixTimestamp ' + modelClass.toUnixInteger().toString();
                        let zoneName = modelClass.zoneName ? modelClass.zoneName : '';
                        value = value + ' Timezone ' + zoneName;
                    }
                }
            }

            if (value != '') {
                fields[fieldName] = value;
            }
        }

        return inertia.render('Reviewer/Dataset/Review', {
            dataset,
            fields: fields,
        });
    }

    public async reviewUpdate({ request, response }: HttpContext) {
        const id = request.param('id');
        // const { id } = params;
        const dataset = await Dataset.findOrFail(id);

        const validStates = ['approved'];
        if (!validStates.includes(dataset.server_state)) {
            // throw new Error('Invalid server state!');
            // return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('reviewer.dataset.list');
        }

        dataset.server_state = 'reviewed';

        try {
            // await dataset.related('editor').associate(user); // speichert schon ab
            await dataset.save();
            return response.toRoute('reviewer.dataset.list').flash('message', `You have successfully reviewed dataset ${dataset.id}!`);
        } catch (error) {
            // Handle any errors
            console.error(error);
            return response.status(500).json({ error: 'An error occurred while reviewing the data.' });
        }
    }

    public async reject({ request, inertia, response }: HttpContext) {
        const id = request.param('id');
        const dataset = await Dataset.query()
            .where('id', id)
            // .preload('titles')
            // .preload('descriptions')
            .preload('editor', (builder) => {
                builder.select('id', 'login', 'email');
            })
            .firstOrFail();

        const validStates = ['approved'];
        if (!validStates.includes(dataset.server_state)) {
            // session.flash('errors', 'Invalid server state!');
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('reviewer.dataset.list');
        }

        return inertia.render('Reviewer/Dataset/Reject', {
            dataset,
        });
    }

    public async rejectUpdate({ request, response, auth }: HttpContext) {
        const authUser = auth.user!;

        const id = request.param('id');
        const dataset = await Dataset.query()
            .where('id', id)
            .preload('editor', (builder) => {
                builder.select('id', 'login', 'email');
            })
            .firstOrFail();

        // const newSchema = schema.create({
        //     server_state: schema.string({ trim: true }),
        //     reject_reviewer_note: schema.string({ trim: true }, [rules.minLength(10), rules.maxLength(500)]),
        // });
        const newSchema = vine.object({
            server_state: vine.string().trim(),
            reject_reviewer_note: vine.string().trim().minLength(10).maxLength(500),
            send_mail: vine.boolean().optional(),
        });

        try {
            // await request.validate({ schema: newSchema });
            const validator = vine.compile(newSchema);
            await request.validateUsing(validator);
        } catch (error) {
            // return response.badRequest(error.messages);
            throw error;
        }

        const validStates = ['approved'];
        if (!validStates.includes(dataset.server_state)) {
            // throw new Error('Invalid server state!');
            // return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
            return response
                .flash(                   
                    `Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
                    'warning',
                )
                .redirect()
                .toRoute('reviewer.dataset.list');
        }

        // dataset.server_state = 'reviewed';
        dataset.server_state = 'rejected_reviewer';
        const rejectReviewerNote = request.input('reject_reviewer_note', '');
        dataset.reject_reviewer_note = rejectReviewerNote;

        // add logic for sending reject message
        const sendMail = request.input('send_email', false);
        // const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
        const validationResult = await validate({
            email: dataset.editor.email,
            validateSMTP: false,
        });
        const validRecipientEmail: boolean = validationResult.valid;
        // let emailStatusMessage = '';

        if (sendMail == true) {
            if (dataset.editor.email && validRecipientEmail) {
                try {
                    await mail.send((message) => {
                        message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
                            <p>Dear editor ${dataset.editor.login},</p>
                            <p>Your approved dataset with ID ${dataset.id} has been rejected.</p>
                            <p>Reason for rejection: ${rejectReviewerNote}</p>
                            <p>Best regards,<br>Your Tethys reviewer: ${authUser.login}</p>
                        `);
                    });
                    // emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
                } catch (error) {
                    logger.error(error);
                    return response
                        .flash('Dataset has not been rejected due to an email error: ' + error.message, 'error')
                        .toRoute('reviewer.dataset.list');
                }
            } else {
                // emailStatusMessage = ` However, the email could not be sent because the editor's email address (${dataset.editor.email}) is not valid.`;
            }
        }

        await dataset.save();

        return response
            .toRoute('reviewer.dataset.list')
            .flash(`You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`, 'message');
    }
}
