const express = require('express');
const cors = require('cors');
const fs = require('fs').promises;
const { Client, GatewayIntentBits } = require('discord.js');
const https = require('https');
const root = express();

const app = express();
root.use('/discord', app)

let config = {};
try {
    try {
        config = require('./config.js');
    } catch (e) {
        config = require('./config.example.js');
    }
} catch (e) {}

const PORT = process.env.PORT || config.PORT || 3000;
const DISCORD_TOKEN = process.env.DISCORD_TOKEN || config.DISCORD_TOKEN || '';
const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID || config.DISCORD_CLIENT_ID || 'YOUR_CLIENT_ID_HERE';
const DISCORD_CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET || config.DISCORD_CLIENT_SECRET || 'YOUR_CLIENT_SECRET_HERE';
const DISCORD_REDIRECT_URI = process.env.DISCORD_REDIRECT_URI || config.DISCORD_REDIRECT_URI || `http://localhost:${PORT}/discord/callback`;
const DISCORD_TOKEN_URL = 'https://discord.com/api/oauth2/token';
const DISCORD_API_BASE = 'https://discord.com/api/v10';

const discordClient = new Client({
    intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildInvites]
});

discordClient.on('error', (error) => {
    console.error('[DISCORD BOT] Error:', error);
});

discordClient.on('disconnect', (event) => {
    console.warn(`[DISCORD BOT] Bot disconnected: ${event.reason} (Code: ${event.code})`);
    setTimeout(() => {
        console.log('[DISCORD BOT] Attempting to reconnect...');
        discordClient.login(DISCORD_TOKEN).catch(error => {
            console.error('[DISCORD BOT] Reconnection failed:', error);
        });
    }, 5000);
});

discordClient.once('ready', () => {
    console.log(`[DISCORD BOT] Bot logged in as ${discordClient.user.tag}`);
    console.log(`[DISCORD BOT] Bot is ready! Connected to ${discordClient.guilds.cache.size} guild(s)`);
});

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const discordUsers = new Map();
const STORAGE_FILE = 'discord_users.json';
const BACKUP_FILE = 'discord_users.backup.json';
const TEMP_FILE = 'discord_users.temp.json';

function validateUserData(userData) {
    if (!userData || typeof userData !== 'object') return false;
    const requiredFields = ['authCode', 'identifier', 'timestamp', 'ip', 'userAgent', 'lastSeen'];
    return requiredFields.every(field => userData.hasOwnProperty(field) && userData[field] !== null && userData[field] !== undefined);
}

async function safeWriteFile(data) {
    try {
        await fs.writeFile(TEMP_FILE, data, 'utf8');
        try {
            await fs.access(STORAGE_FILE);
            await fs.copyFile(STORAGE_FILE, BACKUP_FILE);
        } catch (error) {}
        await fs.rename(TEMP_FILE, STORAGE_FILE);
        try {
            const backupStats = await fs.stat(BACKUP_FILE);
            const backupAge = Date.now() - backupStats.mtime.getTime();
            if (backupAge > 24 * 60 * 60 * 1000) {
                await fs.unlink(BACKUP_FILE);
            }
        } catch (error) {}
        return true;
    } catch (error) {
        console.error('[DISCORD] Error in safe file write:', error);
        try {
            await fs.access(BACKUP_FILE);
            await fs.copyFile(BACKUP_FILE, STORAGE_FILE);
        } catch (restoreError) {}
        try {
            await fs.unlink(TEMP_FILE);
        } catch (cleanupError) {}
        return false;
    }
}

async function loadUsers() {
    try {
        const data = await fs.readFile(STORAGE_FILE, 'utf8');
        const users = JSON.parse(data);
        const validUsers = {};
        let corruptedCount = 0;
        
        for (const [key, userData] of Object.entries(users)) {
            if (validateUserData(userData)) {
                validUsers[key] = userData;
            } else {
                corruptedCount++;
            }
        }
        
        if (corruptedCount > 0) {
            try {
                const backupData = await fs.readFile(BACKUP_FILE, 'utf8');
                const backupUsers = JSON.parse(backupData);
                discordUsers.clear();
                Object.keys(backupUsers).forEach(key => {
                    if (validateUserData(backupUsers[key])) {
                        discordUsers.set(key, backupUsers[key]);
                    }
                });
                await saveUsers();
                return;
            } catch (backupError) {}
        }
        
        discordUsers.clear();
        Object.keys(validUsers).forEach(key => discordUsers.set(key, validUsers[key]));
        
        if (corruptedCount > 0) {
            await saveUsers();
        }
        
        return validUsers;
    } catch (error) {
        if (error.code === 'ENOENT') {
            return {};
        }
        try {
            const backupData = await fs.readFile(BACKUP_FILE, 'utf8');
            const backupUsers = JSON.parse(backupData);
            discordUsers.clear();
            Object.keys(backupUsers).forEach(key => {
                if (validateUserData(backupUsers[key])) {
                    discordUsers.set(key, backupUsers[key]);
                }
            });
            return backupUsers;
        } catch (backupError) {
            return {};
        }
    }
}

async function saveUsers() {
    try {
        const users = Object.fromEntries(discordUsers);
        const data = JSON.stringify(users, null, 2);
        
        if (!data || data === '{}') {
            return false;
        }
        
        try {
            JSON.parse(data);
        } catch (parseError) {
            return false;
        }
        
        return await safeWriteFile(data);
    } catch (error) {
        try {
            const emergencyData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
            await fs.writeFile(`${STORAGE_FILE}.emergency`, emergencyData, 'utf8');
        } catch (emergencyError) {}
        return false;
    }
}

async function safeUserOperation(operation, userId, userData = null) {
    try {
        let result;
        
        switch (operation) {
            case 'add':
                if (!validateUserData(userData)) {
                    throw new Error('Invalid user data provided');
                }
                discordUsers.set(userId, userData);
                result = await saveUsers();
                if (!result) {
                    throw new Error('Failed to save user data');
                }
                break;
                
            case 'remove':
                if (!discordUsers.has(userId)) {
                    throw new Error('User not found');
                }
                discordUsers.delete(userId);
                result = await saveUsers();
                if (!result) {
                    throw new Error('Failed to save after user removal');
                }
                break;
                
            case 'update':
                if (!discordUsers.has(userId)) {
                    throw new Error('User not found for update');
                }
                const existingUser = discordUsers.get(userId);
                const updatedUser = { ...existingUser, ...userData, lastSeen: new Date().toISOString() };
                
                if (!validateUserData(updatedUser)) {
                    throw new Error('Invalid updated user data');
                }
                
                discordUsers.set(userId, updatedUser);
                result = await saveUsers();
                if (!result) {
                    throw new Error('Failed to save updated user data');
                }
                break;
                
            default:
                throw new Error('Invalid operation specified');
        }
        
        return { success: true, result };
    } catch (error) {
        try {
            await loadUsers();
        } catch (reloadError) {}
        return { success: false, error: error.message };
    }
}

loadUsers().then(() => {
    console.log(`[DISCORD] Loaded ${discordUsers.size} users from storage`);
    if (discordUsers.size > 0) {
        console.log(`[DISCORD] Existing user IDs:`, Array.from(discordUsers.keys()).join(', '));
    }
});

app.get('/callback', async (req, res) => {
    const { code, error } = req.query;
    
    if (error) {
        console.error('[DISCORD OAuth] Error from Discord:', error);
        return res.status(400).send(`
            <html>
                <head><title>Discord Authorization Error</title></head>
                <body style="font-family: Arial; text-align: center; padding: 50px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white;">
                    <div style="background: rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 30px; max-width: 500px; margin: 0 auto;">
                        <div style="font-size: 64px; margin: 20px 0;">✗</div>
                        <h1 style="color: #fee2e2;">Discord Authorization Failed</h1>
                        <p>Error: ${error}</p>
                        <p>Please try again.</p>
                    </div>
                </body>
            </html>
        `);
    }
    
    if (!code) {
        console.error('[DISCORD OAuth] No code provided in callback');
        return res.status(400).send(`
            <html>
                <head><title>Discord Authorization Error</title></head>
                <body style="font-family: Arial; text-align: center; padding: 50px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white;">
                    <div style="background: rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 30px; max-width: 500px; margin: 0 auto;">
                        <div style="font-size: 64px; margin: 20px 0;">✗</div>
                        <h1 style="color: #fee2e2;">Discord Authorization Error</h1>
                        <p>No authorization code received.</p>
                        <p>Please try again.</p>
                    </div>
                </body>
            </html>
        `);
    }
    
    console.log('[DISCORD OAuth] Received authorization code:', code);
    
    // Exchange code for token and get user info
    try {
        const tokenData = await exchangeCodeForToken(code);
        
        if (!tokenData || !tokenData.access_token) {
            throw new Error('Failed to exchange code for token');
        }
        
        const discordUserInfo = await getDiscordUserInfo(tokenData.access_token);
        console.log('[DISCORD OAuth] User authorized:', discordUserInfo.username || discordUserInfo.id);
        
        // Redirect to success page with code in query string so client can retrieve it
        res.redirect(`/discord/rpc-success?code=${encodeURIComponent(code)}`);
    } catch (error) {
        console.error('[DISCORD OAuth] Error processing callback:', error.message);
        res.status(500).send(`
            <html>
                <head><title>Discord Authorization Error</title></head>
                <body style="font-family: Arial; text-align: center; padding: 50px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white;">
                    <div style="background: rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 30px; max-width: 500px; margin: 0 auto;">
                        <div style="font-size: 64px; margin: 20px 0;">✗</div>
                        <h1 style="color: #fee2e2;">Discord Authorization Error</h1>
                        <p>Failed to process authorization: ${error.message}</p>
                        <p>Please try again.</p>
                    </div>
                </body>
            </html>
        `);
    }
});

async function exchangeCodeForToken(code, redirectUri = null) {
    const finalRedirectUri = redirectUri || DISCORD_REDIRECT_URI;
    
    return new Promise((resolve, reject) => {
        const data = new URLSearchParams({
            client_id: DISCORD_CLIENT_ID,
            client_secret: DISCORD_CLIENT_SECRET,
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: finalRedirectUri
        });
        
        const options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': data.toString().length
            }
        };
        
        const req = https.request(DISCORD_TOKEN_URL, options, (res) => {
            let responseData = '';
            
            res.on('data', (chunk) => {
                responseData += chunk;
            });
            
            res.on('end', () => {
                if (res.statusCode === 200) {
                    try {
                        const tokenData = JSON.parse(responseData);
                        resolve(tokenData);
                    } catch (error) {
                        reject(new Error('Failed to parse token response'));
                    }
                } else {
                    reject(new Error(`Token exchange failed: ${res.statusCode} - ${responseData}`));
                }
            });
        });
        
        req.on('error', (error) => {
            reject(error);
        });
        
        req.write(data.toString());
        req.end();
    });
}

async function getDiscordUserInfo(accessToken) {
    return new Promise((resolve, reject) => {
        const options = {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        };
        
        const req = https.request(`${DISCORD_API_BASE}/users/@me`, options, (res) => {
            let responseData = '';
            
            res.on('data', (chunk) => {
                responseData += chunk;
            });
            
            res.on('end', () => {
                if (res.statusCode === 200) {
                    try {
                        const userInfo = JSON.parse(responseData);
                        resolve(userInfo);
                    } catch (error) {
                        reject(new Error('Failed to parse user info response'));
                    }
                } else {
                    reject(new Error(`Get user info failed: ${res.statusCode} - ${responseData}`));
                }
            });
        });
        
        req.on('error', (error) => {
            reject(error);
        });
        
        req.end();
    });
}

app.post('/game/discord.php', async (req, res) => {
    const { work } = req.query;
    const { identifier, userId, authCode } = req.body;
    
    try {
        switch (work) {
            case 'validate':
                console.log(`[VALIDATE] userId: ${userId}, identifier: ${identifier || 'none'}, userExists: ${discordUsers.has(userId)}`);
                
                if (discordUsers.has(userId)) {
                    const user = discordUsers.get(userId);
                    console.log(`[VALIDATE] User ${userId} already authorized, returning success`);
                    res.json({ 
                        success: true, 
                        userId: userId,
                        message: 'User already authorized',
                        timestamp: user.timestamp,
                        identifier: user.identifier
                    });
                } else {
                    console.log(`[VALIDATE] User ${userId} NOT found, returning 404 to trigger authorization`);
                    res.status(404).send('HTTP 404');
                }
                break;
                
            case 'register':
                console.log('register', authCode, userId);
                if (!authCode || !userId) {
                    res.status(400).json({ 
                        success: false, 
                        error: 'Missing authCode or userId'
                    });
                    return;
                }
                
                let accessToken = null;
                let refreshToken = null;
                let discordUserInfo = null;
                
                try {
                    const tokenData = await exchangeCodeForToken(authCode);
                    
                    if (tokenData && tokenData.access_token) {
                        accessToken = tokenData.access_token;
                        refreshToken = tokenData.refresh_token || null;
                        discordUserInfo = await getDiscordUserInfo(accessToken);
                    }
                } catch (error) {
                    console.error('[DISCORD] Failed to exchange code:', error.message);
                }
                
                const userData = {
                    authCode: authCode,
                    accessToken: accessToken || null,
                    refreshToken: refreshToken || null,
                    identifier: identifier || 'unknown',
                    timestamp: new Date().toISOString(),
                    ip: req.ip || 'unknown',
                    userAgent: req.get('User-Agent') || 'unknown',
                    lastSeen: new Date().toISOString(),
                    discordUsername: discordUserInfo?.username || null,
                    discordDiscriminator: discordUserInfo?.discriminator || null,
                    discordAvatar: discordUserInfo?.avatar || null
                };
                
                const addResult = await safeUserOperation('add', userId, userData);
                
                if (!addResult.success) {
                    res.status(500).json({
                        success: false,
                        error: 'Failed to register user',
                        details: addResult.error
                    });
                    return;
                }
                
                res.json({ 
                    success: true, 
                    userId: userId,
                    message: 'User registered successfully',
                    timestamp: userData.timestamp
                });
                break;
                
            case 'disconnect':
                if (!userId) {
                    res.status(400).json({ 
                        success: false, 
                        error: 'Missing userId' 
                    });
                    return;
                }
                
                console.log(`[DISCONNECT] Removing user ${userId}`);
                const removeResult = await safeUserOperation('remove', userId);
                
                if (!removeResult.success) {
                    res.status(500).json({
                        success: false,
                        error: 'Failed to disconnect user',
                        details: removeResult.error
                    });
                    return;
                }
                
                console.log(`[DISCONNECT] User ${userId} removed successfully`);
                res.json({ 
                    success: true, 
                    message: 'User disconnected successfully',
                    userId: userId
                });
                break;
                
            case 'check':
                if (!userId) {
                    res.status(400).json({ 
                        success: false, 
                        error: 'Missing userId' 
                    });
                    return;
                }
                
                const exists = discordUsers.has(userId);
                const userInfo = exists ? discordUsers.get(userId) : null;
                
                res.json({
                    success: true,
                    userId: userId,
                    exists: exists,
                    user: userInfo ? {
                        identifier: userInfo.identifier,
                        timestamp: userInfo.timestamp,
                        lastSeen: userInfo.lastSeen,
                        discordUsername: userInfo.discordUsername
                    } : null,
                    totalUsers: discordUsers.size,
                    allUserIds: Array.from(discordUsers.keys())
                });
                break;
                
            case 'status':
                const users = Array.from(discordUsers.entries()).map(([id, data]) => ({
                    userId: id,
                    identifier: data.identifier,
                    timestamp: data.timestamp,
                    lastSeen: data.lastSeen,
                    ip: data.ip
                }));
                
                res.json({
                    success: true,
                    status: 'running',
                    timestamp: new Date().toISOString(),
                    serverInfo: {
                        uptime: process.uptime(),
                        memory: process.memoryUsage(),
                        nodeVersion: process.version,
                        platform: process.platform
                    },
                    connectedUsers: users,
                    totalUsers: discordUsers.size
                });
                break;
                
            default:
                res.status(400).json({ 
                    success: false, 
                    error: 'Invalid work parameter',
                    validWork: ['validate', 'register', 'disconnect', 'status', 'check']
                });
                break;
        }
    } catch (error) {
        console.error(`[DISCORD] Error processing ${work} request:`, error);
        res.status(500).json({ 
            success: false, 
            error: 'Internal server error',
            message: error.message
        });
    }
});

app.get('/game/discord.php', async (req, res) => {
    const { work } = req.query;
    
    if (work === 'status') {
        const users = Array.from(discordUsers.entries()).map(([id, data]) => ({
            userId: id,
            identifier: data.identifier,
            timestamp: data.timestamp,
            lastSeen: data.lastSeen,
            ip: data.ip
        }));
        
        res.json({
            success: true,
            status: 'running',
            timestamp: new Date().toISOString(),
            serverInfo: {
                uptime: process.uptime(),
                memory: process.memoryUsage(),
                nodeVersion: process.version,
                platform: process.platform
            },
            connectedUsers: users,
            totalUsers: discordUsers.size
        });
    } else {
        res.status(400).json({ 
            success: false, 
            error: 'Invalid work parameter for GET request',
            validWork: ['status']
        });
    }
});

app.get('/rpc-success', (req, res) => {
    const { code } = req.query;
    res.send(`
        <html>
            <head><title>Discord Authorization Success</title></head>
            <body style="font-family: Arial; text-align: center; padding: 50px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
                <div style="background: rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 30px; max-width: 500px; margin: 0 auto;">
                    <div style="font-size: 64px; margin: 20px 0;">✓</div>
                    <h1 style="color: #4ade80;">Discord Authorization Successful!</h1>
                    <p>Your Discord account has been successfully linked.</p>
                    <p>You can now close this window and return to the game.</p>
                    ${code ? `<p style="font-size: 12px; opacity: 0.7;">Authorization code: ${code.substring(0, 20)}...</p>` : ''}
                </div>
            </body>
        </html>
    `);
});

app.get('/status', (req, res) => {
    try {
        const botStatus = {
            isReady: discordClient.isReady(),
            isLoggedIn: !!discordClient.user,
            uptime: discordClient.uptime,
            ping: discordClient.ws.ping,
            guilds: discordClient.guilds.cache.size,
            channels: discordClient.channels.cache.size,
            users: discordClient.users.cache.size
        };
        
        const availableGuilds = Array.from(discordClient.guilds.cache.values()).map(guild => ({
            id: guild.id,
            name: guild.name,
            memberCount: guild.memberCount,
            available: guild.available
        }));
        
        res.json({
            success: true,
            timestamp: new Date().toISOString(),
            bot: botStatus,
            availableGuilds: availableGuilds
        });
    } catch (error) {
        console.error('[DISCORD] Error in bot status check:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to check bot status',
            message: error.message
        });
    }
});

app.get('/health', (req, res) => {
    res.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        discordUsers: discordUsers.size
    });
});

app.get('/', (req, res) => {
    res.json({
        name: 'FiveM Discord Authentication Server',
        version: '1.0.0',
        description: 'Handles Discord authentication for FiveM launcher',
        endpoints: {
            'POST /discord/game/discord.php?work=validate': 'Validate Discord user',
            'POST /discord/game/discord.php?work=register': 'Register Discord user',
            'POST /discord/game/discord.php?work=disconnect': 'Disconnect Discord user',
            'POST /discord/game/discord.php?work=status': 'Get server status',
            'GET /discord/game/discord.php?work=status': 'Get server status (GET)',
            'GET /discord/callback': 'Discord OAuth callback endpoint',
            'GET /discord/rpc-success': 'RPC success page',
            'GET /discord/health': 'Health check',
            'GET /discord/status': 'Discord bot status'
        }
    });
});

app.use((error, req, res, next) => {
    console.error('[DISCORD] Unhandled error:', error);
    res.status(500).json({
        success: false,
        error: 'Internal server error',
        message: error.message
    });
});

app.use('*', (req, res) => {
    res.status(404).json({
        success: false,
        error: 'Endpoint not found',
        requestedUrl: req.originalUrl
    });
});

process.on('SIGINT', async () => {
    console.log('\n[DISCORD] Shutting down gracefully...');
    try {
        const saveResult = await saveUsers();
        if (!saveResult) {
            try {
                const emergencyData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
                await fs.writeFile(`${STORAGE_FILE}.shutdown`, emergencyData, 'utf8');
            } catch (emergencyError) {}
        }
        try {
            if (discordUsers.size > 0) {
                const backupData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
                await fs.writeFile(`${BACKUP_FILE}.shutdown`, backupData, 'utf8');
            }
        } catch (backupError) {}
    } catch (error) {
        console.error('[DISCORD] Error during graceful shutdown:', error);
    }
    process.exit(0);
});

process.on('SIGTERM', async () => {
    console.log('\n[DISCORD] Received SIGTERM, shutting down...');
    try {
        const saveResult = await saveUsers();
        if (!saveResult) {
            try {
                const emergencyData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
                await fs.writeFile(`${STORAGE_FILE}.sigterm`, emergencyData, 'utf8');
            } catch (emergencyError) {}
        }
        try {
            if (discordUsers.size > 0) {
                const backupData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
                await fs.writeFile(`${BACKUP_FILE}.sigterm`, backupData, 'utf8');
            }
        } catch (backupError) {}
    } catch (error) {
        console.error('[DISCORD] Error during SIGTERM shutdown:', error);
    }
    process.exit(0);
});

process.on('uncaughtException', async (error) => {
    console.error('[DISCORD] Uncaught Exception:', error);
    try {
        if (discordUsers.size > 0) {
            const emergencyData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
            await fs.writeFile(`${STORAGE_FILE}.emergency`, emergencyData, 'utf8');
        }
    } catch (saveError) {}
    process.exit(1);
});

process.on('unhandledRejection', async (reason, promise) => {
    console.error('[DISCORD] Unhandled Rejection at:', promise, 'reason:', reason);
    try {
        if (discordUsers.size > 0) {
            const emergencyData = JSON.stringify(Object.fromEntries(discordUsers), null, 2);
            await fs.writeFile(`${STORAGE_FILE}.rejection`, emergencyData, 'utf8');
        }
    } catch (saveError) {}
    process.exit(1);
});

discordClient.login(DISCORD_TOKEN).catch(error => {
    console.error('[DISCORD BOT] Failed to login:', error);
});

root.listen(PORT, () => {
    console.log('========================================');
    console.log('FiveM Discord Authentication Server');
    console.log('========================================');
    console.log(`Server running on http://localhost:${PORT}`);
    console.log(`Application root: /discord`);
    console.log(`Available endpoints:`);
    console.log(`  - POST /discord/game/discord.php?work=validate`);
    console.log(`  - POST /discord/game/discord.php?work=register`);
    console.log(`  - POST /discord/game/discord.php?work=disconnect`);
    console.log(`  - POST /discord/game/discord.php?work=status`);
    console.log(`  - GET  /discord/callback`);
    console.log(`  - GET  /discord/rpc-success`);
    console.log(`  - GET  /discord/health`);
    console.log(`  - GET  /discord/status`);
    console.log('========================================');
    console.log('Press Ctrl+C to stop the server');
    console.log('========================================');
});

module.exports = app;
