<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/db/config.php';

use Discord\Discord;
use Discord\Voice\VoiceClient;
use Discord\Parts\Channel\Channel;
use Discord\WebSockets\Event;
use Discord\WebSockets\Intents;
use Discord\Builders\CommandBuilder;
use Discord\Builders\MessageBuilder;
use Discord\Parts\Interactions\Interaction;
use Discord\Parts\Interactions\Command\Option;
use React\ChildProcess\Process;

$db = db();
// Ensure tables exist
$db->exec("CREATE TABLE IF NOT EXISTS bot_logs (id INT AUTO_INCREMENT PRIMARY KEY, message TEXT, log_level VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
$db->exec("CREATE TABLE IF NOT EXISTS bot_settings (setting_key VARCHAR(255) PRIMARY KEY, setting_value TEXT)");
$db->exec("CREATE TABLE IF NOT EXISTS bot_alarms (id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) UNIQUE, guild_id VARCHAR(255), channel_id VARCHAR(255), alarm_time TIME, audio_url TEXT, is_active TINYINT(1) DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");

// Default settings if not present
$stmt = $db->prepare("INSERT IGNORE INTO bot_settings (setting_key, setting_value) VALUES (?, ?)");
$stmt->execute(['sahur_time', '03:00']);
$stmt->execute(['voice_channel_id', '1457687430189682781']);
$stmt->execute(['sahur_source', 'sahur.mp3']);
$stmt->execute(['bot_token', '']); // User should set this manually or it might be already set

$settings = $db->query("SELECT setting_key, setting_value FROM bot_settings")->fetchAll(PDO::FETCH_KEY_PAIR);

$token = $settings['bot_token'] ?? '';
if (empty($token)) {
    // Try to get from env if missing in DB
    $token = getenv('BOT_TOKEN') ?: '';
}

if (empty($token)) {
    echo "Error: Bot token is missing. Please set it in bot_settings table or as BOT_TOKEN env var.\n";
    exit(1);
}

$discord = new Discord([
    'token' => $token,
    'intents' => Intents::getDefaultIntents() | Intents::GUILD_VOICE_STATES | Intents::MESSAGE_CONTENT,
]);

$voiceClient = null;
$ffmpegPath = trim((string)shell_exec("command -v ffmpeg"));

function logToDb($message, $level = 'info') {
    try {
        $db = db();
        $stmt = $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES (?, ?)");
        $stmt->execute([$message, $level]);
    } catch (Exception $e) {
        echo "Log failed: " . $e->getMessage() . "\n";
    }
}

$discord->on('ready', function (Discord $discord) {
    echo "Bot is ready as " . $discord->user->username . "#" . $discord->user->discriminator . PHP_EOL;
    logToDb("Bot is online and ready: " . $discord->user->username);

    $db = db();
    $db->prepare("INSERT INTO bot_settings (setting_key, setting_value) VALUES ('bot_status', 'online') ON DUPLICATE KEY UPDATE setting_value = 'online'")->execute();

    // Periodic timer for Sahur and Alarms
    $discord->getLoop()->addPeriodicTimer(60, function () use ($discord) {
        $now = date('H:i');
        $db = db();
        
        $settings = $db->query("SELECT setting_key, setting_value FROM bot_settings")->fetchAll(PDO::FETCH_KEY_PAIR);
        $sahurTime = $settings['sahur_time'] ?? '03:00';
        $vcId = $settings['voice_channel_id'] ?? '1457687430189682781';

        if ($now === $sahurTime) {
            echo "Sahur time triggered ($now)\n";
            logToDb("Sahur time triggered.");
            playSahur($discord, $vcId);
        }

        $stmt = $db->prepare("SELECT * FROM bot_alarms WHERE alarm_time LIKE ? AND is_active = 1");
        $stmt->execute([$now . '%']);
        $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        foreach ($alarms as $alarm) {
            echo "Alarm triggered for user {$alarm['user_id']} at {$alarm['alarm_time']}\n";
            logToDb("Alarm triggered for user {$alarm['user_id']}");
            playAlarm($discord, $alarm);
        }
    });

    // Register Commands
    registerCommands($discord);
});

function registerCommands(Discord $discord) {
    $commands = [
        CommandBuilder::new()->setName('join')->setDescription('Join your current voice channel'),
        CommandBuilder::new()->setName('out')->setDescription('Leave the voice channel'),
        CommandBuilder::new()->setName('status')->setDescription('Check bot status'),
        CommandBuilder::new()->setName('play')->setDescription('Play music from URL')
            ->addOption((new Option($discord))->setName('url')->setDescription('YouTube/TikTok/SoundCloud URL')->setType(Option::STRING)->setRequired(true)),
        CommandBuilder::new()->setName('stop')->setDescription('Stop music'),
        CommandBuilder::new()->setName('settime')->setDescription('Set your personal alarm time (HH:MM)')
            ->addOption((new Option($discord))->setName('time')->setDescription('Time in HH:MM format')->setType(Option::STRING)->setRequired(true)),
        CommandBuilder::new()->setName('setalarm')->setDescription('Set your personal alarm audio link')
            ->addOption((new Option($discord))->setName('link')->setDescription('Audio URL for the alarm')->setType(Option::STRING)->setRequired(true)),
        CommandBuilder::new()->setName('help')->setDescription('Show help information'),
    ];

    foreach ($commands as $command) {
        $discord->application->commands->save($discord->application->commands->create($command->toArray()));
    }
}

function streamAudio(VoiceClient $vc, string $url, $interaction = null) {
    global $discord;
    echo "Streaming audio: $url\n";
    if ($interaction) {
        $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("🎶 Loading audio info from link..."));
    }

    $safeUrl = escapeshellarg($url);
    $process = new Process("yt-dlp --no-warnings --no-check-certificates --print \"%(title)s\" --print \"%(url)s\" -f \"ba/b\" --no-playlist --js-runtimes node $safeUrl");
    
    try {
        $process->start($discord->getLoop());
    } catch (\Exception $e) {
        echo "Failed to start yt-dlp process: " . $e->getMessage() . "\n";
        if ($interaction) {
            $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Failed to start audio downloader."));
        }
        return;
    }

    $timer = $discord->getLoop()->addTimer(60.0, function() use ($process, $interaction) {
        if ($process->isRunning()) {
            echo "yt-dlp process timed out after 60s\n";
            $process->terminate(SIGKILL);
            if ($interaction) {
                $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Audio loading timed out (60s)."));
            }
        }
    });

    $output = '';
    $errorOutput = '';

    $process->stdout->on('data', function ($chunk) use (&$output) {
        $output .= $chunk;
    });

    $process->stderr->on('data', function ($chunk) use (&$errorOutput) {
        $errorOutput .= $chunk;
    });

    $process->on('exit', function ($code) use (&$output, &$errorOutput, $vc, $interaction, $url, $discord, $timer) {
        $discord->getLoop()->cancelTimer($timer);
        
        $lines = array_values(array_filter(explode("\n", trim($output)), function($line) {
            return !empty(trim($line)) && strpos($line, 'WARNING:') !== 0;
        }));

        if ($code === 0 && count($lines) >= 2) {
            $title = $lines[0];
            $streamUrl = $lines[count($lines) - 1]; // Take the last line as URL, in case there are other prints
            echo "Title: $title, Stream URL found: $streamUrl\n";
            if ($interaction) {
                $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("🎶 **Now Playing:** $title"));
            }

            $playFunc = function () use ($vc, $streamUrl, $title, $interaction) {
                $isReady = method_exists($vc, 'isReady') ? $vc->isReady() : $vc->ready;
                echo "Attempting to play $title. VC Ready: " . ($isReady ? 'Yes' : 'No') . "\n";
                
                if (!$isReady) {
                    echo "Voice client not ready yet for $title, waiting for ready event...\n";
                    $vc->once('ready', function() use ($vc, $streamUrl, $title, $interaction) {
                        echo "Voice client finally ready for $title, playing now.\n";
                        $vc->playFile($streamUrl);
                    });
                    return;
                }

                $vc->playFile($streamUrl)->then(function() use ($title) {
                    echo "Finished playing $title\n";
                }, function($e) use ($vc, $streamUrl, $title, $interaction) {
                    echo "Error playing $title: " . $e->getMessage() . "\n";
                    logToDb("Error playing $title: " . $e->getMessage(), 'error');
                    
                    if (strpos($e->getMessage(), 'not ready') !== false) {
                        echo "Retrying $title in 2 seconds due to 'not ready' error...\n";
                        global $discord;
                        $discord->getLoop()->addTimer(2.0, function() use ($vc, $streamUrl, $title, $interaction) {
                            $vc->playFile($streamUrl);
                        });
                        return;
                    }

                    if ($interaction) {
                        $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Error playing: $title. " . $e->getMessage()));
                    }
                });
            };

            $isReady = method_exists($vc, 'isReady') ? $vc->isReady() : $vc->ready;
            if ($isReady) {
                $playFunc();
            } else {
                echo "Voice client not ready yet for $title (initial check), waiting for ready event...\n";
                $vc->once('ready', $playFunc);
                
                // Safety timeout: if not ready in 15s, fail
                global $discord;
                $discord->getLoop()->addTimer(15.0, function () use ($vc, $interaction, $title, $playFunc) {
                    $isReady = method_exists($vc, 'isReady') ? $vc->isReady() : $vc->ready;
                    if (!$isReady) {
                        $vc->removeListener('ready', $playFunc);
                        echo "Timed out waiting for voice client to be ready for $title\n";
                        if ($interaction) {
                            $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Error: Voice client timed out waiting to be ready."));
                        }
                    }
                });
            }
        } else {
            echo "Failed to fetch stream URL for $url. Code: $code\nError Output: $errorOutput\n";
            if ($interaction) {
                $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Failed to fetch audio. Make sure the link is valid. (Code $code)"));
            }
        }
    });
}

function playSahur(Discord $discord, $vcId) {
    $channel = $discord->getChannel($vcId);
    if (!$channel) return;

    $discord->joinVoiceChannel($channel)->then(function (VoiceClient $vc) {
        $db = db();
        $source = $db->query("SELECT setting_value FROM bot_settings WHERE setting_key = 'sahur_source'")->fetchColumn() ?: 'sahur.mp3';
        if (filter_var($source, FILTER_VALIDATE_URL)) {
            streamAudio($vc, $source);
        } else if (file_exists($source)) {
            if ($vc->ready) {
                $vc->playFile($source);
            } else {
                $vc->once('ready', function () use ($vc, $source) {
                    $vc->playFile($source);
                });
            }
        }
    });
}

function playAlarm(Discord $discord, $alarm) {
    $channel = $discord->getChannel($alarm['channel_id']);
    if (!$channel) return;

    $discord->joinVoiceChannel($channel)->then(function (VoiceClient $vc) use ($alarm) {
        streamAudio($vc, $alarm['audio_url']);
    });
}

$discord->on(Event::INTERACTION_CREATE, function (Interaction $interaction, Discord $discord) use (&$voiceClient) {
    if ($interaction->type !== 2) return;
    
    $command = $interaction->data->name;
    logToDb("Received interaction: $command from " . $interaction->member->user->username);

    switch ($command) {
        case 'help':
            $interaction->respondWithMessage(MessageBuilder::new()->setContent(
                "**AsepSahur Bot Commands:**\n" .
                "`/join` - Join your voice channel\n" .
                "`/play [url]` - Play music from URL\n" .
                "`/stop` - Stop current playback\n" .
                "`/settime [HH:MM]` - Set your alarm time\n" .
                "`/setalarm [url]` - Set your alarm audio\n" .
                "`/status` - Check bot status\n" .
                "`/out` - Make bot leave voice channel"
            ));
            break;

        case 'status':
            $vc = $discord->getVoiceClient($interaction->guild_id);
            $status = "Bot is online. " . ($vc ? "Connected to " . $vc->channel->name : "Not in voice channel.");
            $interaction->respondWithMessage(MessageBuilder::new()->setContent($status));
            break;

        case 'join':
            $userChannel = $interaction->member->getVoiceChannel();
            if (!$userChannel) {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("You must be in a voice channel!"));
                return;
            }
            $interaction->acknowledge();
            $discord->joinVoiceChannel($userChannel)->then(function (VoiceClient $vc) use ($interaction, $userChannel) {
                $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Joined " . $vc->channel->name));
                $vc->on('close', function() use ($userChannel) {
                    echo "Voice client closed for channel " . $userChannel->name . "\n";
                    logToDb("Voice client closed for channel " . $userChannel->name);
                });
                $vc->on('error', function($e) use ($userChannel) {
                    echo "Voice client error in channel " . $userChannel->name . ": " . $e->getMessage() . "\n";
                    logToDb("Voice client error in channel " . $userChannel->name . ": " . $e->getMessage(), 'error');
                });
            }, function ($e) use ($interaction) {
                $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Failed to join: " . $e->getMessage()));
            });
            break;

        case 'out':
            $vc = $discord->getVoiceClient($interaction->guild_id);
            if ($vc) {
                $vc->close();
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Left voice channel."));
            } else {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("I'm not in a voice channel."));
            }
            break;

        case 'play':
            $url = $interaction->data->options['url']->value;
            $userChannel = $interaction->member->getVoiceChannel();
            if (!$userChannel) {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Join a VC first!"));
                return;
            }

            $interaction->acknowledge();
            
            $vc = $discord->getVoiceClient($interaction->guild_id);
            
            if ($vc && $vc->channel->id === $userChannel->id) {
                echo "Already in correct channel. Checking readiness...\n";
                streamAudio($vc, $url, $interaction);
            } else {
                if ($vc) {
                    echo "Voice client in wrong channel or state, closing...\n";
                    $vc->close();
                }
                
                echo "Joining channel: " . $userChannel->name . "\n";
                // Small delay if we just closed
                $delay = $vc ? 0.8 : 0.1;
                $discord->getLoop()->addTimer($delay, function() use ($discord, $userChannel, $interaction, $url) {
                    $discord->joinVoiceChannel($userChannel)->then(function (VoiceClient $vc) use ($interaction, $url, $userChannel) {
                        echo "Joined voice channel, now streaming...\n";
                        $vc->on('close', function() use ($userChannel) {
                            echo "Voice client closed for channel " . $userChannel->name . "\n";
                            logToDb("Voice client closed for channel " . $userChannel->name);
                        });
                        $vc->on('error', function($e) use ($userChannel) {
                            echo "Voice client error in channel " . $userChannel->name . ": " . $e->getMessage() . "\n";
                            logToDb("Voice client error in channel " . $userChannel->name . ": " . $e->getMessage(), 'error');
                        });
                        streamAudio($vc, $url, $interaction);
                    }, function ($e) use ($interaction) {
                        echo "Error joining VC: " . $e->getMessage() . "\n";
                        $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("❌ Error joining voice channel: " . $e->getMessage()));
                    });
                });
            }
            break;

        case 'stop':
            $vc = $discord->getVoiceClient($interaction->guild_id);
            if ($vc) {
                $vc->stop();
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Stopped playback."));
            } else {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Nothing is playing."));
            }
            break;

        case 'settime':
            $time = $interaction->data->options['time']->value;
            if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Invalid format. Use HH:MM (e.g. 03:00)"));
                return;
            }
            $userId = (string)$interaction->member->id;
            $guildId = (string)$interaction->guild_id;
            $channelId = (string)($interaction->member->getVoiceChannel()->id ?? '');
            
            if (empty($channelId)) {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Please join a voice channel first so I know where to wake you up!"));
                return;
            }

            $db = db();
            $stmt = $db->prepare("INSERT INTO bot_alarms (user_id, guild_id, channel_id, alarm_time, audio_url) 
                                 VALUES (?, ?, ?, ?, 'sahur.mp3') 
                                 ON DUPLICATE KEY UPDATE alarm_time = ?, guild_id = ?, channel_id = ?");
            $stmt->execute([$userId, $guildId, $channelId, $time, $time, $guildId, $channelId]);
            $interaction->respondWithMessage(MessageBuilder::new()->setContent("Alarm time set to $time. I will join " . $interaction->member->getVoiceChannel()->name . " at that time."));
            break;

        case 'setalarm':
            $link = $interaction->data->options['link']->value;
            $userId = (string)$interaction->member->id;
            $guildId = (string)$interaction->guild_id;
            $channelId = (string)($interaction->member->getVoiceChannel()->id ?? '');

            if (empty($channelId)) {
                $interaction->respondWithMessage(MessageBuilder::new()->setContent("Please join a voice channel first!"));
                return;
            }

            $db = db();
            $stmt = $db->prepare("INSERT INTO bot_alarms (user_id, guild_id, channel_id, alarm_time, audio_url) 
                                 VALUES (?, ?, ?, '03:00', ?) 
                                 ON DUPLICATE KEY UPDATE audio_url = ?, guild_id = ?, channel_id = ?");
            $stmt->execute([$userId, $guildId, $channelId, $link, $link, $guildId, $channelId]);
            $interaction->respondWithMessage(MessageBuilder::new()->setContent("Alarm audio set."));
            break;
    }
});

$discord->run();
