import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
// import { RenderResponse } from '@ioc:EidelLev/Inertia';
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
import hash from '@adonisjs/core/services/hash';
// import { schema, rules } from '@adonisjs/validator';
import vine from '@vinejs/vine';
import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage';
import path from 'path';
import crypto from 'crypto';
// import drive from '#services/drive';
import drive from '@adonisjs/drive/services/main';
import logger from '@adonisjs/core/services/logger';

// Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database.
export default class UserController {
    /**
     * Show the user a form to change their personal information & password.
     *
     * @return — \Inertia\Response
     */
    public async accountInfo({ inertia, auth }: HttpContext) {
        // const user = auth.user;
        const user = (await User.find(auth.user?.id)) as User;
        // const id = request.param('id');
        // const user = await User.query().where('id', id).firstOrFail();

        let storage = new BackupCodeStorage(new SecureRandom());
        // const codes=  user.isTwoFactorEnabled? (await user.getBackupCodes()).map((role) => role.code) : [];
        let backupState = await storage.getBackupCodesState(user);

        return inertia.render('Auth/AccountInfo', {
            user: user,
            twoFactorEnabled: user.isTwoFactorEnabled,
            // code: await TwoFactorAuthProvider.generateQrCode(user),
            backupState: backupState,
        });
    }

    public async accountInfoStore({ auth, request, response, session }: HttpContext) {
        // const passwordSchema = schema.create({
        //     old_password: schema.string({ trim: true }, [rules.required()]),
        //     new_password: schema.string({ trim: true }, [rules.minLength(8), rules.maxLength(255), rules.confirmed('confirm_password')]),
        //     confirm_password: schema.string({ trim: true }, [rules.required()]),
        // });
        const passwordSchema = vine.object({
            // first step
            old_password: vine.string().trim(),
            // .regex(/^[a-zA-Z0-9]+$/),
            new_password: vine.string().confirmed({ confirmationField: 'confirm_password' }).trim().minLength(8).maxLength(255),
        });
        try {
            // await request.validate({ schema: passwordSchema });
            const validator = vine.compile(passwordSchema);
            await request.validateUsing(validator);
        } catch (error) {
            // return response.badRequest(error.messages);
            throw error;
        }

        try {
            const user = (await auth.user) as User;
            const { old_password, new_password } = request.only(['old_password', 'new_password']);

            // if (!(old_password && new_password && confirm_password)) {
            //     return response.status(400).send({ warning: 'Old password and new password are required.' });
            // }

            // Verify if the provided old password matches the user's current password
            const isSame = await hash.verify(user.password, old_password);
            if (!isSame) {
                return response.flash('warning', 'Old password is incorrect.').redirect().back();
            }

            // Hash the new password before updating the user's password
            user.password = new_password;
            await user.save();

            // return response.status(200).send({ message: 'Password updated successfully.' });
            session.flash({ message: 'Password updated successfully.' });
            return response.redirect().toRoute('settings.user');
        } catch (error) {
            // return response.status(500).send({ message: 'Internal server error.' });
            return response.flash('warning', `Invalid server state. Internal server error.`).redirect().back();
        }
    }

    public async profile({ inertia, auth }: HttpContext) {
        const user = await User.find(auth.user?.id);
        // let test = await drive.use().getUrl(user?.avatar);
        // user?.preload('roles');
        const avatarFullPathUrl = user?.avatar ? await drive.use('public').getUrl(user.avatar) : null;
        return inertia.render('profile/show', {
            user: user,
            defaultUrl: avatarFullPathUrl,
        });
    }

    /**
     * Update the user's profile information.
     *
     * @param {HttpContext} ctx - The HTTP context object.
     * @returns {Promise<void>}
     */
    public async profileUpdate({ auth, request, response, session }: HttpContext) {
        if (!auth.user) {
            session.flash('error', 'You must be logged in to update your profile.');
            return response.redirect().toRoute('login');
        }

        const updateProfileValidator = vine.withMetaData<{ userId: number }>().compile(
            vine.object({
                first_name: vine.string().trim().minLength(4).maxLength(255),
                last_name: vine.string().trim().minLength(4).maxLength(255),
                login: vine.string().trim().minLength(4).maxLength(255),
                email: vine
                    .string()
                    .trim()
                    .maxLength(255)
                    .email()
                    .normalizeEmail()
                    .isUnique({ table: 'accounts', column: 'email', whereNot: (field) => field.meta.userId }),
                avatar: vine
                    .myfile({
                        size: '2mb',
                        extnames: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],
                    })
                    // .allowedMimetypeExtensions({
                    //     allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],
                    // })
                    .optional(),
            }),
        );

        const user = await User.find(auth.user.id);
        if (!user) {
            session.flash('error', 'User not found.');
            return response.redirect().toRoute('login');
        }

        try {
            // validate update form
            await request.validateUsing(updateProfileValidator, {
                meta: {
                    userId: user.id,
                },
            });

            const { login, email, first_name, last_name } = request.only(['login', 'email', 'first_name', 'last_name']);
            const sanitizedData: { [key: string]: any } = {
                login: login?.trim(),
                email: email?.toLowerCase().trim(),
                first_name: first_name?.trim(),
                last_name: last_name?.trim(),
                // avatar: "",
            };
            const toCamelCase = (str: string) => str.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
            const hasInputChanges = Object.keys(sanitizedData).some((key) => {
                const camelKey = toCamelCase(key);
                return sanitizedData[key] !== (user.$attributes as { [key: string]: any })[camelKey];
            });

            let hasAvatarChanged = false;
            const avatar = request.file('avatar');
            if (avatar) {
                const fileHash = crypto
                    .createHash('sha256')
                    .update(avatar.clientName + avatar.size)
                    .digest('hex');
                const fileName = `avatar-${fileHash}.${avatar.extname}`;
                const avatarFullPath = path.join('/uploads', `${user.login}`, fileName);

                if (user.avatar != avatarFullPath) {
                    if (user.avatar) {
                        await drive.use('public').delete(user.avatar);
                    }
                    hasAvatarChanged = user.avatar !== avatarFullPath;
                    await avatar.moveToDisk(avatarFullPath, 'public', {
                        name: fileName,
                        overwrite: true, // overwrite in case of conflict
                        disk: 'public',
                    });
                    sanitizedData.avatar = avatarFullPath;
                }
            }

            if (!hasInputChanges && !hasAvatarChanged) {
                session.flash('message', 'No changes were made.');
                return response.redirect().back();
            }

            await user.merge(sanitizedData).save();
            session.flash('message', 'User has been updated successfully');
            return response.redirect().toRoute('settings.profile.edit');
        } catch (error) {
            logger.error('Profile update failed:', error);
            // session.flash('errors', 'Profile update failed. Please try again.');
            // return response.redirect().back();
            throw error;
        }
    }

    public async passwordUpdate({ auth, request, response, session }: HttpContext) {
        // const passwordSchema = schema.create({
        //     old_password: schema.string({ trim: true }, [rules.required()]),
        //     new_password: schema.string({ trim: true }, [rules.minLength(8), rules.maxLength(255), rules.confirmed('confirm_password')]),
        //     confirm_password: schema.string({ trim: true }, [rules.required()]),
        // });
        const passwordSchema = vine.object({
            // first step
            old_password: vine.string().trim(),
            // .regex(/^[a-zA-Z0-9]+$/),
            new_password: vine.string().confirmed({ confirmationField: 'confirm_password' }).trim().minLength(8).maxLength(255),
        });
        try {
            // await request.validate({ schema: passwordSchema });
            const validator = vine.compile(passwordSchema);
            await request.validateUsing(validator);
        } catch (error) {
            // return response.badRequest(error.messages);
            throw error;
        }

        try {
            const user = (await auth.user) as User;
            const { old_password, new_password } = request.only(['old_password', 'new_password']);

            // if (!(old_password && new_password && confirm_password)) {
            //     return response.status(400).send({ warning: 'Old password and new password are required.' });
            // }

            // Verify if the provided old password matches the user's current password
            const isSame = await hash.verify(user.password, old_password);
            if (!isSame) {
                session.flash('warning', 'Old password is incorrect.');
                return response.redirect().back();
                // return response.flash('warning', 'Old password is incorrect.').redirect().back();
            }

            // Hash the new password before updating the user's password
            user.password = new_password;
            await user.save();

            // return response.status(200).send({ message: 'Password updated successfully.' });
            session.flash({ message: 'Password updated successfully.' });
            return response.redirect().toRoute('settings.profile.edit');
        } catch (error) {
            // return response.status(500).send({ message: 'Internal server error.' });
            return response.flash('warning', `Invalid server state. Internal server error.`).redirect().back();
        }
    }

    public async enableTwoFactorAuthentication({ auth, response, session }: HttpContext): Promise<void> {
        // const user: User | undefined = auth?.user;
        const user = (await User.find(auth.user?.id)) as User;

        user.twoFactorSecret = TwoFactorAuthProvider.generateSecret(user);
        user.twoFactorRecoveryCodes = await TwoFactorAuthProvider.generateRecoveryCodes();
        await user.save();

        session.flash('message', 'Two factor authentication enabled.');
        return response.redirect().back();
        // return inertia.render('Auth/AccountInfo', {
        //     // status: {
        //     //     type: 'success',
        //     //     message: 'Two factor authentication enabled.',
        //     // },
        //     user: user,
        //     twoFactorEnabled: user.isTwoFactorEnabled,
        //     code: await TwoFactorAuthProvider.generateQrCode(user),
        //     recoveryCodes: user.twoFactorRecoveryCodes,
        // });
    }

    public async disableTwoFactorAuthentication({ auth, response, session }: HttpContext): Promise<void> {
        const user: User | undefined = auth.user;

        if (user) {
            user.twoFactorSecret = null;
            user.twoFactorRecoveryCodes = null;
            await user.save();
            session.flash('message', 'Two-factor authentication disabled.');
        } else {
            session.flash('error', 'User not found.');
        }

        return response.redirect().back();
        // return inertia.render('Auth/AccountInfo', {
        //     // status: {
        //     //     type: 'success',
        //     //     message: 'Two factor authentication disabled.',
        //     // },
        //     user: user,
        //     twoFactorEnabled: user.isTwoFactorEnabled,
        // });
    }

    // public async fetchRecoveryCodes({ auth, view }) {
    //     const user = auth?.user;

    //     return view.render('pages/settings', {
    //         twoFactorEnabled: user.isTwoFactorEnabled,
    //         recoveryCodes: user.twoFactorRecoveryCodes,
    //     });
    // }
}
