const fs = require('fs');
const path = require('path');
const { Op } = require('sequelize');
const { User, Session, sequelize, OTP } = require('../models');
const crypto = require('crypto');
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
const config = require('../config');
const logger = require('../utils/logger');
const {
    NotFoundError,
    ConflictError,
    BadRequestError,
    UnauthorizedError,
} = require('../utils/errors');
const otpService = require('./otpService');
const { generateRandomString } = require('../utils/helpers');

class ProfileService {
    sanitizeUser(user) {
        if (!user) return null;

        const plain = typeof user.toJSON === 'function' ? user.toJSON() : { ...user };

        delete plain.password;
        delete plain.pin;
        delete plain.twoFactorSecret;
        delete plain.backupCodes;
        delete plain.emailVerificationToken;
        delete plain.passwordResetToken;
        delete plain.passwordResetExpires;

        const avatar = plain.avatar || plain.profileImage || null;

        return {
            ...plain,
            avatar,
            profileImage: avatar,
        };
    }

    buildProfileCompletion(user) {
        const items = [
            !!user?.firstName,
            !!user?.lastName,
            !!user?.email,
            !!user?.phone,
            !!user?.country,
            !!user?.city,
            !!user?.currency,
        ];

        const completedCount = items.filter(Boolean).length;
        const total = items.length;
        const percent = total ? Math.round((completedCount / total) * 100) : 0;

        return {
            completed: completedCount,
            total,
            percent,
        };
    }

    buildSecuritySummary(user) {
        const checks = {
            passwordSet: !!user?.password,
            twoFactorEnabled: !!user?.twoFactorEnabled,
            emailVerified: !!user?.emailVerified,
            phoneVerified: !!user?.phoneVerified,
        };

        const score =
            Number(checks.passwordSet) +
            Number(checks.twoFactorEnabled) +
            Number(checks.emailVerified) +
            Number(checks.phoneVerified);

        let level = 'Weak';
        if (score >= 4) level = 'Strong';
        else if (score >= 2) level = 'Moderate';

        return {
            level,
            checks,
            status: 'Protected',
        };
    }

    buildVerificationSummary(user) {
        const kycStatus = user?.kycStatus || 'unverified';

        return {
            kycStatus,
            emailVerified: !!user?.emailVerified,
            phoneVerified: !!user?.phoneVerified,
            compliant: ['verified', 'level1', 'level2', 'level3', 'level4'].includes(
                String(kycStatus).toLowerCase()
            ),
            needsAttention: !['verified', 'level1', 'level2', 'level3', 'level4'].includes(
                String(kycStatus).toLowerCase()
            ),
        };
    }

    async getOverview(userId) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const safeUser = this.sanitizeUser(user);

        const fullName =
            `${safeUser.firstName || ''} ${safeUser.lastName || ''}`.trim() || 'User';

        const initials =
            `${safeUser.firstName?.[0] || ''}${safeUser.lastName?.[0] || ''}`.toUpperCase() || 'U';

        const profileCompletion = this.buildProfileCompletion(safeUser);
        const security = this.buildSecuritySummary(user);
        const verification = this.buildVerificationSummary(safeUser);

        return {
            profile: {
                id: safeUser.id,
                traderId: safeUser.traderId || null,
                fullName,
                initials,
                firstName: safeUser.firstName || '',
                lastName: safeUser.lastName || '',
                username: safeUser.username || '',
                email: safeUser.email || '',
                phone: safeUser.phone || '',
                country: safeUser.country || '',
                city: safeUser.city || '',
                currency: safeUser.currency || 'USD',
                avatar: safeUser.avatar || null,
                profileImage: safeUser.profileImage || null,
                createdAt: safeUser.createdAt || null,
                joinDate: safeUser.createdAt || null,
                accountType: safeUser.accountType || 'Premium',
                lastLogin: safeUser.lastLogin || null,
            },
            profileCompletion,
            security,
            verification,
            accountSnapshot: {
                fullName,
                username: safeUser.username || '',
                currency: safeUser.currency || 'USD',
                country: safeUser.country || '',
                city: safeUser.city || '',
            },
            settingsCards: {
                profile: {
                    email: safeUser.email || 'Not set',
                    phone: safeUser.phone || 'Not set',
                },
                security: {
                    twoFactorEnabled: !!safeUser.twoFactorEnabled,
                    status: security.status,
                },
                notifications: {
                    tradingAlerts: 'Active',
                    emailUpdates: 'Enabled',
                },
                api: {
                    keys: 'Manage access',
                    permissions: 'Restricted',
                },
                verification: {
                    kyc: safeUser.kycStatus || 'unverified',
                    account: verification.compliant ? 'Compliant' : 'Pending',
                },
            },
        };
    }

    async getProfile(userId) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        return this.sanitizeUser(user);
    }

    normalizeProfilePayload(payload = {}) {
        const profile = payload.profile || {};
        const contact = payload.contact || {};
        const preferences = payload.preferences || {};
        const location = payload.location || {};

        return {
            firstName: payload.firstName ?? profile.firstName,
            lastName: payload.lastName ?? profile.lastName,
            bio: payload.bio ?? profile.bio,

            email: payload.email ?? contact.email,
            phone: payload.phone ?? contact.phone,

            timezone: payload.timezone ?? preferences.timezone,
            language: payload.language ?? preferences.language,
            currency: payload.currency ?? preferences.currency,
            dateFormat: payload.dateFormat ?? preferences.dateFormat,

            country: payload.country ?? location.country,
            city: payload.city ?? location.city,

            username: payload.username,
        };
    }

    async updateProfile(userId, payload = {}) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const normalized = this.normalizeProfilePayload(payload);

        const allowedFields = [
            'firstName',
            'lastName',
            'email',
            'phone',
            'country',
            'city',
            'timezone',
            'language',
            'currency',
            'dateFormat',
            'bio',
            'username',
        ];

        const updates = {};

        for (const field of allowedFields) {
            if (normalized[field] !== undefined) {
                updates[field] = typeof normalized[field] === 'string'
                    ? normalized[field].trim()
                    : normalized[field];
            }
        }

        if (updates.email) {
            const existingEmail = await User.findOne({
                where: {
                    email: updates.email,
                    id: { [Op.ne]: userId },
                },
            });

            if (existingEmail) {
                throw new ConflictError('Email already in use');
            }
        }

        if (updates.username) {
            const existingUsername = await User.findOne({
                where: {
                    username: updates.username,
                    id: { [Op.ne]: userId },
                },
            });

            if (existingUsername) {
                throw new ConflictError('Username already taken');
            }
        }

        await user.update(updates);

        const refreshed = await User.findByPk(userId);
        return this.sanitizeUser(refreshed);
    }

    async uploadAvatar(userId, file) {
        if (!file) {
            throw new BadRequestError('Avatar file is required');
        }

        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const uploadsDir = path.join(process.cwd(), 'uploads', 'avatars');
        fs.mkdirSync(uploadsDir, { recursive: true });

        const ext = path.extname(file.originalname || '').toLowerCase() || '.jpg';
        const safeExt = ['.jpg', '.jpeg', '.png', '.webp'].includes(ext) ? ext : '.jpg';
        const fileName = `avatar_${userId}_${Date.now()}${safeExt}`;
        const filePath = path.join(uploadsDir, fileName);

        fs.writeFileSync(filePath, file.buffer);

        const avatarUrl = `/uploads/avatars/${fileName}`;

        if (user.avatar) {
            const oldPath = path.join(process.cwd(), user.avatar.replace(/^\//, ''));
            if (fs.existsSync(oldPath)) {
                try {
                    fs.unlinkSync(oldPath);
                } catch (err) {
                    // ignore
                }
            }
        }

        await user.update({
            avatar: avatarUrl,
            profileImage: avatarUrl,
        });

        const refreshed = await User.findByPk(userId);
        const safeUser = this.sanitizeUser(refreshed);

        return {
            avatar: safeUser.avatar,
            profileImage: safeUser.profileImage,
            user: safeUser,
        };
    }

    async deleteAvatar(userId) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        if (user.avatar) {
            const oldPath = path.join(process.cwd(), user.avatar.replace(/^\//, ''));
            if (fs.existsSync(oldPath)) {
                try {
                    fs.unlinkSync(oldPath);
                } catch (err) {
                    // ignore
                }
            }
        }

        await user.update({
            avatar: null,
            profileImage: null,
        });

        return this.sanitizeUser(await User.findByPk(userId));
    }

    async getSecurityOverview(userId, currentSessionId = null) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const sessions = await Session.findAll({
            where: { userId, isActive: true },
            order: [['lastActiveAt', 'DESC']],
        });

        const loginlog = (user.loginHistory || []).map((item, index) => ({
            id: item.id || `${userId}-${index + 1}`,
            date: item.date || item.createdAt || item.time || '—',
            createdAt: item.createdAt || null,
            device: item.device || 'Unknown Device',
            ip: item.ip || '—',
            location: item.location || 'Unknown',
            status: item.status || 'success',
        }));

        const activeSessions = sessions.map((session) => ({
            id: session.id,
            device: session.device || session.browser || 'Unknown Device',
            browser: session.browser || null,
            ip: session.ip || '—',
            location: session.location || 'Unknown',
            lastActive: session.lastActiveAt
                ? new Date(session.lastActiveAt).toLocaleString()
                : 'Recently',
            lastActiveAt: session.lastActiveAt,
            createdAt: session.createdAt,
            current:
                String(session.id) === String(currentSessionId) ||
                !!session.isCurrent,
        }));

        const securityScoreParts = [
            !!user.password,
            !!user.emailVerified,
            !!user.phoneVerified,
            !!user.twoFactorEnabled,
            !!user.antiPhishingCode,
            !!user.withdrawalWhitelist,
            !!user.loginNotifications,
        ];

        const securityScore = Math.round(
            (securityScoreParts.filter(Boolean).length / securityScoreParts.length) * 100
        );

        let securityMessage = 'Your account needs more protection';
        if (securityScore >= 85) securityMessage = 'Your account is well protected';
        else if (securityScore >= 60) securityMessage = 'Your account protection is decent';

        const passwordStrength = user.passwordStrength || (user.password ? 'strong' : 'weak');

        let twoFASetup = {};
        if (!user.twoFactorEnabled) {
            const secret = speakeasy.generateSecret({
                name: `${config.twoFA.appName}:${user.email}`,
                length: 32,
            });

            const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);

            await user.update({
                twoFactorSecret: secret.base32,
                twoFactorMethod: 'authenticator',
            });

            twoFASetup = {
                qrCodeUrl,
                manualEntryCode: secret.base32,
                method: 'authenticator',
            };
        } else {
            twoFASetup = {
                qrCodeUrl: user.twoFAQrCode || '',
                manualEntryCode: user.twoFactorSecret || '',
                method: user.twoFactorMethod || 'authenticator',
            };
        }

        return {
            loginlog,
            activeSessions,
            backupCodes: Array.isArray(user.backupCodes) ? user.backupCodes : [],
            twoFASetup,
            security: {
                passwordStrength,
                lastPasswordChange: user.passwordChangedAt || '—',
                twoFactorEnabled: !!user.twoFactorEnabled,
                twoFactorMethod: user.twoFactorMethod || 'authenticator',
                emailVerified: !!user.emailVerified,
                phoneVerified: !!user.phoneVerified,
                antiPhishingEnabled: !!user.antiPhishingCode,
                antiPhishingCode: user.antiPhishingCode || '',
                withdrawalWhitelist: !!user.withdrawalWhitelist,
                lastPinChange:  user.pinChangedAt || '—',
                loginType: user.loginWith,
                loginNotifications:
                    typeof user.loginNotifications === 'boolean'
                        ? user.loginNotifications
                        : true,
                securityScore,
                securityMessage,
            },
        };
    }

    async changePassword(userId, currentPassword, newPassword) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const isMatch = await user.comparePassword(currentPassword);
        if (!isMatch) {
            throw new UnauthorizedError('Current password is incorrect');
        }

        await user.update({
            password: newPassword,
            passwordChangedAt: new Date(),
        });

        logger.info(`Password changed: ${user.email}`);

        return {
            lastPasswordChange: 'Just now',
        };
    }

    async changePin(userId, currentPin, newPin) {
        const user = await User.findByPk(userId);
        
        if (!user) {
            throw new NotFoundError('User not found');
        }

        const isMatch = (parseInt(user.pin) === parseInt(currentPin));
        if (!isMatch) {
            throw new UnauthorizedError(`Current pin is incorrect ${user.pin} isMatch: ${isMatch}`);
        }

        await user.update({
            pin: newPin,
            pinChangedAt: new Date(),
        });

        logger.info(`Pin changed: ${user.email}`);

        return {
            lastPinChange: 'Just now',
        };
    }

    async updateAntiPhishing(userId, antiPhishingEnabled, antiPhishingCode) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        await user.update({
            antiPhishingCode: antiPhishingEnabled ? antiPhishingCode.trim() : null,
        });

        return {
            antiPhishingEnabled: !!antiPhishingEnabled,
            antiPhishingCode: antiPhishingEnabled ? antiPhishingCode.trim() : '',
        };
    }

    async updateWithdrawalWhitelist(userId, withdrawalWhitelist) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        await user.update({
            withdrawalWhitelist: !!withdrawalWhitelist,
        });

        return {
            withdrawalWhitelist: !!withdrawalWhitelist,
        };
    }

    async updateLoginType(userId, currentPassword, loginWith) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        const isMatch = await user.comparePassword(currentPassword);
        if (!isMatch) {
            throw new UnauthorizedError('Password is incorrect');
        }

        await user.update({
            loginWith,
        });

        return {
            loginWith,
        };
    }

    async updateLoginNotifications(userId, loginNotifications) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        await user.update({
            loginNotifications: !!loginNotifications,
        });

        return {
            loginNotifications: !!loginNotifications,
        };
    }

    async verifyPassword2fa(userId, data, ip, device) {
        const { password, type } = data;

        if (!['email', 'authenticator'].includes(type)) {
            throw new BadRequestError('Invalid 2FA type');
        }

        const user = await User.findByPk(userId);
        if (!user) {
            throw new NotFoundError('User not found');
        }

        const isMatch = await user.comparePassword(password);
        if (!isMatch) {
            throw new UnauthorizedError('Current password is incorrect');
        }

        // =========================
        // IF 2FA NOT ENABLED (SETUP)
        // =========================
        if (!user.twoFactorEnabled) {

            // 🔐 AUTHENTICATOR SETUP
            if (type === 'authenticator') {

                const secret = speakeasy.generateSecret({
                    name: `${config.twoFA.appName}:${user.email}`,
                    length: 32,
                });

                const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);

                await user.update({
                    twoFactorTempSecret: secret.base32, // TEMP storage
                    twoFactorMethod: 'authenticator',
                });

                return {
                    qrCodeUrl,
                    manualEntryCode: secret.base32,
                    method: 'authenticator',
                    message: 'Scan QR and verify code to activate'
                };
            }

            // 📧 EMAIL OTP SETUP
            if (type === 'email') {
                console.log("I am here");
                await otpService.generateOTP(userId, "2fa", ip, device);

                return {
                    method: 'email',
                    message: 'OTP sent to your email'
                };
            }
        }

        // =========================
        // IF 2FA ALREADY ENABLED
        // =========================
        return {
            message: '2FA already enabled. Use verify endpoint.',
        };
    }


    async enable2FA(userId, data) {
        const { code, method } = data;

        const transaction = await sequelize.transaction();

        try {
            const user = await User.findByPk(userId, { transaction });

            if (!user) {
                throw new NotFoundError('User not found');
            }

            const backupCodes = Array.from({ length: 10 }, () =>
                generateRandomString(8).toUpperCase()
            );

            // =========================
            // AUTHENTICATOR FLOW
            // =========================
            if (method === 'authenticator') {

                if (!user.twoFactorTempSecret) {
                    throw new BadRequestError('Please setup 2FA first');
                }

                const isValid = speakeasy.totp.verify({
                    secret: user.twoFactorTempSecret,
                    encoding: 'base32',
                    token: code,
                    window: 1,
                });

                if (!isValid) {
                    throw new BadRequestError('Invalid 2FA code');
                }

                await user.update({
                    twoFactorEnabled: true,
                    twoFactorSecret: user.twoFactorTempSecret,
                    twoFactorTempSecret: null, // ✅ VERY IMPORTANT
                    twoFactorMethod: 'authenticator',
                    backupCodes,
                }, { transaction });

                await transaction.commit();

                logger.info(`2FA enabled (authenticator): ${user.email}`);

                return {
                    twoFactorEnabled: true,
                    twoFactorMethod: 'authenticator',
                    backupCodes,
                };
            }

            // =========================
            // EMAIL OTP FLOW
            // =========================
            if (method === 'email') {

                const otpres = await otpService.verifyOTP(userId, '2fa', code);

                if (!otpres?.status) {
                    throw new BadRequestError('Invalid 2FA code');
                }

                await user.update({
                    twoFactorEnabled: true,
                    twoFactorMethod: 'email',
                    backupCodes,
                }, { transaction });

                await transaction.commit();

                logger.info(`2FA enabled (email): ${user.email}`);

                return {
                    twoFactorEnabled: true,
                    twoFactorMethod: 'email', // ✅ FIXED
                    backupCodes,
                };
            }

            throw new BadRequestError('Invalid 2FA method');

        } catch (error) {
            await transaction.rollback();

            logger.error(`Enable 2FA failed: ${error.message}`);

            throw error;
        }
    }


    async disable2FA(userId, data) {
        const { password, code } = data
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        if (!user.twoFactorEnabled) {
            throw new BadRequestError('2FA is not enabled');
        }

        const isMatch = await user.comparePassword(password);
        if (!isMatch) {
            throw new UnauthorizedError('Current password is incorrect');
        }


        if (user.twoFactorMethod === 'authenticator') {

            if (!user.twoFactorSecret) {
                throw new BadRequestError('Please setup 2FA first');
            }

            const isValid = speakeasy.totp.verify({
                secret: user.twoFactorSecret,
                encoding: 'base32',
                token: code,
                window: 1,
            });

            if (!isValid) {
                throw new BadRequestError('Invalid 2FA code');
            }

        }


        // =========================
        // EMAIL OTP FLOW
        // =========================
        if (user.twoFactorMethod === 'email') {

            const otpres = await otpService.verifyOTP(userId, '2fa', code);

            if (!otpres?.status) {
                throw new BadRequestError('Invalid 2FA code');
            }
        }

        await user.update({
            twoFactorEnabled: false,
            twoFactorMethod: null,
            twoFactorSecret: null,
            backupCodes: [],
        });

        logger.info(`2FA disabled: ${user.email}`);

        return {
            twoFactorEnabled: false,
            twoFactorMethod: null,
        };
    }

    async resend2fa(userId, ip, device) {


        const otp = await OTP.findOne({
            where: {
                userId,
                type: '2fa',
            },
            order: [['createdAt', 'DESC']],
        });

        if (otp) {
            const now = new Date();
            const diffSeconds = (now - new Date(otp.createdAt)) / 1000;

            if (diffSeconds < 60) {
                throw new BadRequestError('Please wait 60 seconds before resending OTP');
            }
        }

        await OTP.invalidatePreviousOTPs(userId, '2fa')

        const token = await otpService.generateOTP(userId, '2fa', ip, device);

        return { message: "Email otp send" };
    }

    async regenerateBackupCodes(userId) {
        const user = await User.findByPk(userId);

        if (!user) {
            throw new NotFoundError('User not found');
        }

        if (!user.twoFactorEnabled) {
            throw new BadRequestError('Enable 2FA before generating backup codes');
        }

        const backupCodes = Array.from({ length: 10 }, () => generateRandomString(8).toUpperCase());

        await user.update({ backupCodes });

        return { backupCodes };
    }

    async revokeSession(userId, sessionId) {
        const session = await Session.findOne({
            where: { id: sessionId, userId },
        });

        if (!session) {
            throw new NotFoundError('Session not found');
        }

        await session.update({
            isActive: false,
            revokedAt: new Date(),
        });

        return true;
    }

    async revokeAllSessions(userId, currentSessionId = null) {
        const where = {
            userId,
            isActive: true,
        };

        if (currentSessionId) {
            where.id = { [Op.ne]: currentSessionId };
        }

        await Session.update(
            {
                isActive: false,
                revokedAt: new Date(),
            },
            { where }
        );

        return true;
    }
}

module.exports = new ProfileService();