import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import User from 'App/Models/User';
import Dataset from 'App/Models/Dataset';
import License from 'App/Models/License';
import Project from 'App/Models/Project';
import Title from 'App/Models/Title';
import Description from 'App/Models/Description';
import Language from 'App/Models/Language';
// import CreateUserValidator from 'App/Validators/CreateUserValidator';
// import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import dayjs from 'dayjs';
import Person from 'App/Models/Person';
import Database from '@ioc:Adonis/Lucid/Database';
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
import Subject from 'App/Models/Subject';
import CreateDatasetValidator from 'App/Validators/CreateDatasetValidator';
import { TitleTypes, DescriptionTypes, ContributorTypes, PersonNameTypes } from 'Contracts/enums';
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';

export default class DatasetController {
    public async index({ auth, request, inertia }: HttpContextContract) {
        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
            .whereIn('server_state', [
                'inprogress',
                'released',
                'editor_accepted',
                'approved',
                'reviewed',
                'rejected_editor',
                'rejected_reviewer',
            ])
            .where('account_id', user.id)
            .preload('titles')
            .preload('user', (query) => query.select('id', 'login'))
            // .preload('titles', (builder) => {
            //     // pull the actual preload data

            //     builder.where('type', 'Main');
            // })
            .paginate(page, 10);

        return inertia.render('Submitter/Dataset/Index', {
            // testing: 'this is a test',
            datasets: myDatasets.serialize(),
            filters: request.all(),
            can: {
                // create: await auth.user?.can(['dataset-submit']),
                edit: await auth.user?.can(['dataset-edit']),
                delete: await auth.user?.can(['dataset-delete']),
            },
        });
    }
    public async create({ inertia }: HttpContextContract) {
        const licenses = await License.query().select('id', 'name_long').pluck('name_long', 'id');

        const projects = await Project.query().pluck('label', 'id');

        const doctypes = {
            analysisdata: { label: 'Analysis', value: 'analysisdata' },
            measurementdata: { label: 'Measurements', value: 'measurementdata' },
            monitoring: 'Monitoring',
            remotesensing: 'Remote Sensing',
            gis: 'GIS',
            models: 'Models',
            mixedtype: 'Mixed Type',
        };

        // const titletypes = {
        //     Sub: 'Sub',
        //     Alternative: 'Alternative',
        //     Translated: 'Translated',
        //     Other: 'Other',
        // };

        // let test = Object.entries(TitleTypes).map(([key, value]) => ({id: key, value: value}))

        // const languages = await Database.from('languages').select('*').where('active', true);
        return inertia.render('Submitter/Dataset/Create', {
            licenses: licenses,
            doctypes: doctypes,
            titletypes: Object.entries(TitleTypes)
                .filter(([value]) => value !== 'Main')
                .map(([key, value]) => ({ value: key, label: value })),
            descriptiontypes: Object.entries(DescriptionTypes)
                .filter(([value]) => value !== 'Abstract')
                .map(([key, value]) => ({ value: key, label: value })),
            // descriptiontypes: DescriptionTypes
            projects: projects,
        });
    }

    public async firstStep({ request, response }: HttpContextContract) {
        const newDatasetSchema = schema.create({
            language: schema.string({ trim: true }, [
                rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
            ]),
            licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
            rights: schema.string([rules.equalTo('true')]),
        });

        try {
            //  Step 2 - Validate request body against  the schema

            await request.validate({ schema: newDatasetSchema, messages: this.messages });
            // console.log({ payload });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }
        return response.redirect().back();
    }

    public async secondStep({ request, response }: HttpContextContract) {
        const newDatasetSchema = schema.create({
            // first step
            language: schema.string({ trim: true }, [
                rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
            ]),
            licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
            rights: schema.string([rules.equalTo('true')]),
            // second step
            type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
            creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
            titles: schema.array([rules.minLength(1)]).members(
                schema.object().members({
                    value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
                    type: schema.enum(Object.values(TitleTypes)),
                    language: schema.string({ trim: true }, [
                        rules.minLength(2),
                        rules.maxLength(255),
                        rules.translatedLanguage('/language', 'type'),
                    ]),
                }),
            ),
            descriptions: schema.array([rules.minLength(1)]).members(
                schema.object().members({
                    value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
                    type: schema.enum(Object.values(DescriptionTypes)),
                    language: schema.string({ trim: true }, [
                        rules.minLength(2),
                        rules.maxLength(255),
                        rules.translatedLanguage('/language', 'type'),
                    ]),
                }),
            ),
            authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),

            // project_id: schema.number(),
        });

        try {
            //  Step 2 - Validate request body against  the schema
            await request.validate({ schema: newDatasetSchema, messages: this.messages });
            // console.log({ payload });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }
        return response.redirect().back();
    }

    public async thirdStep({ request, response }: HttpContextContract) {
        const newDatasetSchema = schema.create({
            // first step
            language: schema.string({ trim: true }, [
                rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
            ]),
            licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
            rights: schema.string([rules.equalTo('true')]),
            // second step
            type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
            creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
            titles: schema.array([rules.minLength(1)]).members(
                schema.object().members({
                    value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
                    type: schema.enum(Object.values(TitleTypes)),
                    language: schema.string({ trim: true }, [
                        rules.minLength(2),
                        rules.maxLength(255),
                        rules.translatedLanguage('/language', 'type'),
                    ]),
                }),
            ),
            descriptions: schema.array([rules.minLength(1)]).members(
                schema.object().members({
                    value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
                    type: schema.enum(Object.values(DescriptionTypes)),
                    language: schema.string({ trim: true }, [
                        rules.minLength(2),
                        rules.maxLength(255),
                        rules.translatedLanguage('/language', 'type'),
                    ]),
                }),
            ),
            authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),
            // third step
            project_id: schema.number.optional(),
            embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
            coverage: schema.object().members({
                x_min: schema.number(),
                x_max: schema.number(),
                y_min: schema.number(),
                y_max: schema.number(),
                elevation_absolut: schema.number.optional(),
                elevation_min: schema.number.optional([rules.requiredIfExists('elevation_max')]),
                elevation_max: schema.number.optional([rules.requiredIfExists('elevation_min')]),
                depth_absolut: schema.number.optional(),
                depth_min: schema.number.optional([rules.requiredIfExists('depth_max')]),
                depth_max: schema.number.optional([rules.requiredIfExists('depth_min')]),
            }),
            subjects: schema.array([rules.minLength(3), rules.uniqueArray('value')]).members(
                schema.object().members({
                    value: schema.string({ trim: true }, [
                        rules.minLength(3),
                        rules.maxLength(255),
                        // rules.unique({ table: 'dataset_subjects', column: 'value' }),
                    ]),
                    // type: schema.enum(Object.values(TitleTypes)),
                    language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
                }),
            ),
        });

        try {
            //  Step 2 - Validate request body against  the schema
            await request.validate({ schema: newDatasetSchema, messages: this.messages });
            // console.log({ payload });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }
        return response.redirect().back();
    }
    public async store({ auth, request, response, session }: HttpContextContract) {
        // node ace make:validator CreateDataset
        try {
            //  Step 2 - Validate request body against  the schema
            // await request.validate(CreateUserValidator);
            // await request.validate({ schema: newDatasetSchema, messages: this.messages });
            await request.validate(CreateDatasetValidator);
            // console.log({ payload });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }

        // const user = new User();
        // user.email = 'example@example.com';
        // user.password = 'password';
        // await user.useTransaction(trx).save();

        let trx: TransactionClientContract | null = null;
        try {
            trx = await Database.transaction();
            const user = (await User.find(auth.user?.id)) as User;

            // const dataset = await user.related('datasets').create({
            //     type: request.input('type'),
            //     creatingCorporation: request.input('creating_corporation'),
            //     language: request.input('language'),
            // });

            // Create a new instance of the Dataset model:
            const dataset = new Dataset();
            dataset.type = request.input('type');
            dataset.creatingCorporation = request.input('creating_corporation');
            dataset.language = request.input('language');
            dataset.embargoDate = request.input('embargo_date');
            //await dataset.related('user').associate(user); // speichert schon ab
            // Dataset.$getRelation('user').boot();
            // Dataset.$getRelation('user').setRelated(dataset, user);
            // dataset.$setRelated('user', user);
            await user.useTransaction(trx).related('datasets').save(dataset);

            //store licenses:
            const licenses: number[] = request.input('licenses', []);
            dataset.useTransaction(trx).related('licenses').sync(licenses);

            const authors = request.input('authors', []);
            for (const [key, person] of authors.entries()) {
                const pivotData = { role: 'author', sort_order: key + 1 };

                if (person.id !== undefined) {
                    await dataset
                        .useTransaction(trx)
                        .related('persons')
                        .attach({
                            [person.id]: {
                                role: pivotData.role,
                                sort_order: pivotData.sort_order,
                                allow_email_contact: false,
                            },
                        });
                } else {
                    const dataPerson = new Person();
                    // use overwritten fill method
                    dataPerson.fill(person);
                    await dataset.useTransaction(trx).related('persons').save(dataPerson, false, {
                        role: pivotData.role,
                        sort_order: pivotData.sort_order,
                        allow_email_contact: false,
                    });
                }
            }

            const contributors = request.input('contributors', []);
            for (const [key, person] of contributors.entries()) {
                const pivotData = { role: 'contributor', sort_order: key + 1 };

                if (person.id !== undefined) {
                    await dataset
                        .useTransaction(trx)
                        .related('persons')
                        .attach({
                            [person.id]: {
                                role: pivotData.role,
                                sort_order: pivotData.sort_order,
                                allow_email_contact: false,
                            },
                        });
                } else {
                    const dataPerson = new Person();
                    // use overwritten fill method
                    dataPerson.fill(person);
                    await dataset.useTransaction(trx).related('persons').save(dataPerson, false, {
                        role: pivotData.role,
                        sort_order: pivotData.sort_order,
                        allow_email_contact: false,
                    });
                }
            }

            //save main and additional titles
            const titles = request.input('titles', []);
            for (const titleData of titles) {
                const title = new Title();
                title.value = titleData.value;
                title.language = titleData.language;
                title.type = titleData.type;
                await dataset.useTransaction(trx).related('titles').save(title);
            }

            //save abstract and additional descriptions
            const descriptions = request.input('descriptions', []);
            for (const descriptionData of descriptions) {
                // $descriptionReference = new Description($description);
                // $dataset->abstracts()->save($descriptionReference);
                const description = new Description();
                description.value = descriptionData.value;
                description.language = descriptionData.language;
                description.type = descriptionData.type;
                await dataset.useTransaction(trx).related('descriptions').save(description);
            }

            //save keywords
            const keywords = request.input('subjects', []);
            for (const keywordData of keywords) {
                // $dataKeyword = new Subject($keyword);
                // $dataset->subjects()->save($dataKeyword);
                const keyword = await Subject.firstOrNew({ value: keywordData.value, type: keywordData.type }, keywordData);
                if (keyword.$isNew === true) {
                    await dataset.useTransaction(trx).related('subjects').save(keyword);
                } else {
                    await dataset.useTransaction(trx).related('subjects').attach([keyword.id]);
                }
            }

            // Dataset.$getRelation('persons').boot();
            // Dataset.$getRelation('persons').pushRelated(dataset, person)
            // dataset.$pushRelated('persons', person);

            await trx.commit();

            console.log('Users and posts created successfully');
        } catch (error) {
            if (trx !== null) {
                await trx.rollback();
            }
            console.error('Failed to create dataset and related models:', error);

            // Handle the error and exit the controller code accordingly
            // session.flash('message', 'Failed to create dataset and relaed models');
            // return response.redirect().back();
            throw error;
        }

        // save data files:
        const coverImage = request.files('files')[0];
        if (coverImage) {
            // clientName: 'Gehaltsschema.png'
            // extname: 'png'
            // fieldName: 'file'
            // size: 135624

            //const datasetFolder = 'files/' . dataset->id;

            // await coverImage.moveToDisk('./')
            await coverImage.moveToDisk(
                '/test_dataset2',
                {
                    name: 'renamed-file-name.jpg',
                    overwrite: true, // overwrite in case of conflict
                },
                'local',
            );
            // let path = coverImage.filePath;
        }

        session.flash('message', 'Dataset has been created successfully');
        // return response.redirect().toRoute('user.index');
        return response.redirect().back();
    }

    public messages: CustomMessages = {
        'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
        'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
        'required': '{{ field }} is required',
        'unique': '{{ field }} must be unique, and this value is already taken',
        // 'confirmed': '{{ field }} is not correct',
        'licences.minLength': 'at least {{ options.minLength }} permission must be defined',
        'licences.*.number': 'Define roles as valid numbers',
        'rights.equalTo': 'you must agree to continue',

        'titles.0.value.minLength': 'Main Title  must be at least {{ options.minLength }} characters long',
        'titles.0.value.required': 'Main Title is required',
        'titles.*.value.required': 'Additional title is required, if defined',
        'titles.*.type.required': 'Additional title type is required',
        'titles.*.language.required': 'Additional title language is required',
        'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',

        'descriptions.0.value.minLength': 'Main Abstract  must be at least {{ options.minLength }} characters long',
        'descriptions.0.value.required': 'Main Abstract is required',
        'descriptions.*.value.required': 'Additional description is required, if defined',
        'descriptions.*.type.required': 'Additional description type is required',
        'descriptions.*.language.required': 'Additional description language is required',
        'descriptions.*.language.translatedLanguage':
            'The language of the translated description must be different from the language of the dataset',

        'authors.minLength': 'at least {{ options.minLength }} author must be defined',

        'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,

        'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined',
        'subjects.uniqueArray': 'The {{ options.array }} array must have unique values based on the {{ options.field }} attribute.',
        'subjects.*.value.required': 'keyword value is required',
        'subjects.*.value.minLength': 'keyword value  must be at least {{ options.minLength }} characters long',
        'subjects.*.type.required': 'keyword type is required',
        'subjects.*.language.required': 'language of keyword is required',

        'files.*.size': 'file size is to big',
        'files.extnames': 'file extension is not supported',
    };

    // public async release({ params, view }) {
    public async release({ request, inertia, response }: HttpContextContract) {
        const id = request.param('id');

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

        const validStates = ['inprogress', 'rejected_editor'];
        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 released to editor. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .back();
        }

        return inertia.render('Submitter/Dataset/Release', {
            dataset,
        });
    }

    public async releaseUpdate({ request, response }: HttpContextContract) {
        const id = request.param('id');
        const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail();

        const validStates = ['inprogress', 'rejected_editor'];
        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 dataset.serialize();
        }

        if (dataset.files.length === 0) {
            return response.flash('warning', 'At least minimum one file is required.').redirect('back');
        }

        const preferation = request.input('preferation', '');
        const preferredReviewer = request.input('preferred_reviewer');
        const preferredReviewerEmail = request.input('preferred_reviewer_email');

        if (preferation === 'yes_preferation') {
            const newSchema = schema.create({
                preferred_reviewer: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
                preferred_reviewer_email: schema.string([rules.email()]),
            });

            try {
                await request.validate({ schema: newSchema });
            } catch (error) {
                // return response.badRequest(error.messages);
                throw error;
            }
        }

        const input = {
            preferred_reviewer: preferredReviewer || null,
            preferred_reviewer_email: preferredReviewerEmail || null,
            server_state: 'released',
            editor_id: null,
            reviewer_id: null,
            reject_editor_note: null,
            reject_reviewer_note: null,
        };

        // Clear editor_id if it exists
        if (dataset.editor_id !== null) {
            input.editor_id = null;
        }

        // Clear reject_editor_note if it exists
        if (dataset.reject_editor_note !== null) {
            input.reject_editor_note = null;
        }

        // Clear reviewer_id if it exists
        if (dataset.reviewer_id !== null) {
            input.reviewer_id = null;
        }

        // Clear reject_reviewer_note if it exists
        if (dataset.reject_reviewer_note !== null) {
            input.reject_reviewer_note = null;
        }

        if (await dataset.merge(input).save()) {
            return response.toRoute('dataset.list').flash('message', 'You have released your dataset!');
        }
        // throw new GeneralException(trans('exceptions.publish.release.update_error'));
    }

    public async edit({ params, inertia }) {
        const dataset = await Dataset.query().where('id', params.id).preload('titles').firstOrFail();

        // await dataset.loadMany([
        //   'licenses',
        //   'authors',
        //   'contributors',
        //   'titles',
        //   'abstracts',
        //   'files',
        //   'coverage',
        //   'subjects',
        //   'references',
        // ]);

        const titleTypes = Object.entries(TitleTypes)
            // .filter(([value]) => value !== 'Main')
            .map(([key, value]) => ({ value: key, label: value }));

        const descriptionTypes = Object.entries(DescriptionTypes)
            // .filter(([value]) => value !== 'Abstract')
            .map(([key, value]) => ({ value: key, label: value }));

        const languages = await Language.query().where('active', true).pluck('part1', 'part1');

        // const contributorTypes = Config.get('enums.contributor_types');
        const contributorTypes = Object.entries(ContributorTypes).map(([key, value]) => ({ value: key, label: value }));

        // const nameTypes = Config.get('enums.name_types');
        const nameTypes = Object.entries(PersonNameTypes).map(([key, value]) => ({ value: key, label: value }));

        // const messages = await Database.table('messages')
        //   .pluck('help_text', 'metadata_element');

        const projects = await Project.query().pluck('label', 'id');

        const currentDate = new Date();
        const currentYear = currentDate.getFullYear();
        const years = Array.from({ length: currentYear - 1990 + 1 }, (_, index) => 1990 + index);

        // const licenses = await License.query()
        //   .select('id', 'name_long', 'link_licence')
        //   .orderBy('sort_order')
        //   .fetch();
        const licenses = await License.query().select('id', 'name_long', 'link_licence').orderBy('sort_order');

        // const checkeds = dataset.licenses.first().id;

        const keywordTypes = {
            uncontrolled: 'uncontrolled',
            swd: 'swd',
        };

        const referenceTypes = ['DOI', 'Handle', 'ISBN', 'ISSN', 'URL', 'URN'];

        const relationTypes = [
            'IsSupplementTo',
            'IsSupplementedBy',
            'IsContinuedBy',
            'Continues',
            'IsNewVersionOf',
            'IsPartOf',
            'HasPart',
            'Compiles',
            'IsVariantFormOf',
        ];

        const doctypes = {
            analysisdata: { label: 'Analysis', value: 'analysisdata' },
            measurementdata: { label: 'Measurements', value: 'measurementdata' },
            monitoring: 'Monitoring',
            remotesensing: 'Remote Sensing',
            gis: 'GIS',
            models: 'Models',
            mixedtype: 'Mixed Type',
        };

        return inertia.render('Submitter/Dataset/Edit', {
            dataset,
            titletypes : titleTypes,
            descriptionTypes,
            contributorTypes,
            nameTypes,
            languages,
            // messages,
            projects,
            licenses,
            // checkeds,
            years,
            // languages,
            keywordTypes,
            referenceTypes,
            relationTypes,
            doctypes,
        });
    }
}
