#!/usr/bin/env node
/**
 * Discord Channel Rename Script
 * 
 * Renames existing server channels to match consistent naming convention
 * based on Pterodactyl server names.
 * 
 * Expected naming:
 *   Category: 🎮 {Server Name}
 *   Text:     {base-name}-chat
 *   Text:     {base-name}-in-game
 *   Forum:    {base-name}-forum
 *   Voice:    {Server Name}
 * 
 * Usage:
 *   DRY_RUN=true node discord-channel-rename.js   # Preview changes
 *   DRY_RUN=false node discord-channel-rename.js  # Execute changes
 * 
 * Created: April 9, 2026
 * Chronicler: #74
 * Principle: "Make the plan. Execute the plan. Expect the plan to go off the rails."
 */

require('dotenv').config({ path: '/opt/arbiter-3.0/.env' });
const { Client, GatewayIntentBits, ChannelType } = require('discord.js');

// ============================================================================
// CONFIGURATION
// ============================================================================

const DRY_RUN = process.env.DRY_RUN !== 'false';  // Default to dry run
const RATE_LIMIT_DELAY = 1500;  // 1.5 seconds between operations

// ============================================================================
// HELPERS
// ============================================================================

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

/**
 * Convert server name to base channel name
 * "Homestead - A Cozy Survival Experience" → "homestead"
 * "All The Mons (Private) - TX" → "all-the-mons"
 * "Stoneblock 4" → "stoneblock-4"
 */
function toBaseName(serverName) {
    return serverName
        .split(' - ')[0]                    // Take part before " - " subtitle
        .replace(/\s*\([^)]*\)\s*/g, '')    // Remove parentheticals
        .replace(/\s*\[[^\]]*\]\s*/g, '')   // Remove brackets like [Dungeons & Dragons]
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, '')        // Remove special chars except spaces
        .replace(/\s+/g, '-')               // Spaces to hyphens
        .replace(/-+/g, '-')                // Multiple hyphens to single
        .replace(/^-|-$/g, '')              // Trim leading/trailing hyphens
        .trim();
}

/**
 * Convert server name to voice channel display name
 * Same as base but keeps proper case and removes subtitles/parentheticals
 */
function toVoiceName(serverName) {
    return serverName
        .split(' - ')[0]
        .replace(/\s*\([^)]*\)\s*/g, '')
        .replace(/\s*\[[^\]]*\]\s*/g, '')
        .trim();
}

/**
 * Fetch Minecraft servers from Pterodactyl
 */
async function fetchPterodactylServers() {
    const PANEL_URL = process.env.PANEL_URL;
    const API_KEY = process.env.PANEL_APPLICATION_KEY;
    const NEST_IDS = (process.env.MINECRAFT_NEST_IDS || '1,5').split(',').map(n => parseInt(n.trim()));

    const response = await fetch(`${PANEL_URL}/api/application/servers?per_page=100`, {
        headers: { 
            'Authorization': `Bearer ${API_KEY}`,
            'Accept': 'application/json'
        }
    });

    if (!response.ok) {
        throw new Error(`Pterodactyl API error: ${response.status}`);
    }

    const data = await response.json();
    
    // Filter to Minecraft servers only
    return data.data
        .filter(s => NEST_IDS.includes(s.attributes.nest))
        .map(s => ({
            name: s.attributes.name,
            identifier: s.attributes.identifier,
            baseName: toBaseName(s.attributes.name),
            voiceName: toVoiceName(s.attributes.name)
        }));
}

/**
 * Find the best matching Discord category for a server
 */
function findCategoryForServer(server, categories) {
    const serverBase = server.baseName;
    const serverVoice = server.voiceName.toLowerCase();
    
    for (const cat of categories) {
        const catName = cat.name.replace(/^🎮\s*/, '').toLowerCase();
        const catBase = toBaseName(cat.name);
        
        // Exact match on base name
        if (catBase === serverBase) return cat;
        
        // Category contains server voice name
        if (catName.includes(serverVoice)) return cat;
        
        // Server voice name contains category name
        if (serverVoice.includes(catBase)) return cat;
    }
    
    return null;
}

/**
 * Determine what a channel should be renamed to based on its type
 */
function getExpectedChannelName(channel, server) {
    const baseName = server.baseName;
    const voiceName = server.voiceName;
    const currentName = channel.name.toLowerCase();
    
    switch (channel.type) {
        case ChannelType.GuildVoice:
            return voiceName;
            
        case ChannelType.GuildForum:
            return `${baseName}-forum`;
            
        case ChannelType.GuildText:
            // Determine if this is chat or in-game based on current name
            if (currentName.includes('in-game') || currentName.includes('ingame')) {
                return `${baseName}-in-game`;
            } else if (currentName.includes('chat') || currentName.includes('general')) {
                return `${baseName}-chat`;
            } else {
                // Default text channel to chat
                return `${baseName}-chat`;
            }
            
        default:
            return null;  // Don't rename unknown types
    }
}

// ============================================================================
// MAIN
// ============================================================================

async function main() {
    console.log('');
    console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    console.log('🔧 Discord Channel Rename Script');
    console.log(`   Mode: ${DRY_RUN ? '🔍 DRY RUN (no changes)' : '⚡ LIVE (will rename channels)'}`);
    console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    console.log('');

    // Initialize Discord client
    const client = new Client({ 
        intents: [GatewayIntentBits.Guilds] 
    });

    try {
        // Login
        console.log('📡 Connecting to Discord...');
        await client.login(process.env.DISCORD_BOT_TOKEN);
        await new Promise(resolve => client.once('ready', resolve));
        console.log(`   Logged in as ${client.user.tag}`);

        // Get guild
        const guild = client.guilds.cache.get(process.env.GUILD_ID);
        if (!guild) {
            throw new Error(`Guild not found: ${process.env.GUILD_ID}`);
        }
        console.log(`   Guild: ${guild.name}`);
        console.log('');

        // Fetch Pterodactyl servers
        console.log('📋 Fetching Pterodactyl servers...');
        const servers = await fetchPterodactylServers();
        console.log(`   Found ${servers.length} Minecraft servers`);
        console.log('');

        // Get all 🎮 categories
        const categories = guild.channels.cache
            .filter(ch => ch.type === ChannelType.GuildCategory && ch.name.includes('🎮'))
            .map(ch => ch);
        console.log(`   Found ${categories.length} game categories in Discord`);
        console.log('');

        // Stats
        const stats = {
            matched: 0,
            unmatched: [],
            renames: [],
            alreadyCorrect: 0,
            errors: []
        };

        // Process each server
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log('PROCESSING SERVERS');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

        for (const server of servers) {
            console.log(`\n📦 ${server.name}`);
            console.log(`   Base: ${server.baseName} | Voice: ${server.voiceName}`);

            // Find matching category
            const category = findCategoryForServer(server, categories);
            
            if (!category) {
                console.log(`   ⚠️  No matching category found`);
                stats.unmatched.push(server.name);
                continue;
            }

            console.log(`   📁 Matched to: ${category.name}`);
            stats.matched++;

            // Get children of this category
            const children = guild.channels.cache.filter(ch => ch.parentId === category.id);

            // Check each child channel
            for (const [, channel] of children) {
                const expectedName = getExpectedChannelName(channel, server);
                
                if (!expectedName) {
                    console.log(`   ⏭️  Skipping ${channel.name} (unknown type: ${channel.type})`);
                    continue;
                }

                if (channel.name === expectedName) {
                    console.log(`   ✓ ${channel.name} (already correct)`);
                    stats.alreadyCorrect++;
                } else {
                    console.log(`   🔄 ${channel.name} → ${expectedName}`);
                    stats.renames.push({
                        from: channel.name,
                        to: expectedName,
                        channelId: channel.id,
                        type: ChannelType[channel.type]
                    });

                    if (!DRY_RUN) {
                        try {
                            await channel.setName(expectedName, 'Discord channel rename script - consistent naming');
                            console.log(`      ✅ Renamed!`);
                            await sleep(RATE_LIMIT_DELAY);
                        } catch (err) {
                            console.log(`      ❌ Error: ${err.message}`);
                            stats.errors.push({ channel: channel.name, error: err.message });
                        }
                    }
                }
            }

            // Check if category itself needs renaming
            const expectedCatName = `🎮 ${server.voiceName}`;
            if (category.name !== expectedCatName) {
                console.log(`   🔄 Category: ${category.name} → ${expectedCatName}`);
                stats.renames.push({
                    from: category.name,
                    to: expectedCatName,
                    channelId: category.id,
                    type: 'Category'
                });

                if (!DRY_RUN) {
                    try {
                        await category.setName(expectedCatName, 'Discord channel rename script - consistent naming');
                        console.log(`      ✅ Renamed!`);
                        await sleep(RATE_LIMIT_DELAY);
                    } catch (err) {
                        console.log(`      ❌ Error: ${err.message}`);
                        stats.errors.push({ channel: category.name, error: err.message });
                    }
                }
            }
        }

        // Summary
        console.log('');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log('SUMMARY');
        console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        console.log(`   Mode: ${DRY_RUN ? 'DRY RUN' : 'LIVE'}`);
        console.log(`   Servers matched: ${stats.matched}`);
        console.log(`   Already correct: ${stats.alreadyCorrect}`);
        console.log(`   Renames ${DRY_RUN ? 'needed' : 'executed'}: ${stats.renames.length}`);
        
        if (stats.unmatched.length > 0) {
            console.log('');
            console.log('   ⚠️  Unmatched servers (no Discord category found):');
            stats.unmatched.forEach(s => console.log(`      - ${s}`));
        }

        if (stats.errors.length > 0) {
            console.log('');
            console.log('   ❌ Errors:');
            stats.errors.forEach(e => console.log(`      - ${e.channel}: ${e.error}`));
        }

        if (DRY_RUN && stats.renames.length > 0) {
            console.log('');
            console.log('   📝 Pending renames:');
            stats.renames.forEach(r => console.log(`      ${r.type}: ${r.from} → ${r.to}`));
            console.log('');
            console.log('   Run with DRY_RUN=false to execute these changes.');
        }

        console.log('');

    } catch (error) {
        console.error('');
        console.error('❌ FATAL ERROR:', error.message);
        if (error.stack) {
            console.error(error.stack);
        }
    } finally {
        client.destroy();
        console.log('👋 Disconnected from Discord.');
    }
}

main();
