import { exec } from "child_process";
import { promisify } from "util";
import { writeFile, readFile, unlink } from "fs/promises";
import { Client, TextChannel } from "discord.js";
import { getPostRestartEmbed, getInstallingDependenciesEmbed } from "./update.view";
import type { PostRestartResult } from "./update.view";

const execAsync = promisify(exec);

// Constants
const STALE_CONTEXT_MS = 10 * 60 * 1000; // 10 minutes

export interface RestartContext {
    channelId: string;
    userId: string;
    timestamp: number;
    runMigrations: boolean;
    installDependencies: boolean;
}

export interface UpdateCheckResult {
    needsInstall: boolean;
    needsMigrations: boolean;
    error?: Error;
}

export class UpdateService {
    private static readonly CONTEXT_FILE = ".restart_context.json";

    static async checkForUpdates(): Promise<{ hasUpdates: boolean; log: string; branch: string }> {
        const { stdout: branchName } = await execAsync("git rev-parse --abbrev-ref HEAD");
        const branch = branchName.trim();

        await execAsync("git fetch --all");
        const { stdout: logOutput } = await execAsync(`git log HEAD..origin/${branch} --oneline`);

        return {
            hasUpdates: !!logOutput.trim(),
            log: logOutput.trim(),
            branch
        };
    }

    static async performUpdate(branch: string): Promise<void> {
        await execAsync(`git reset --hard origin/${branch}`);
    }

    static async checkUpdateRequirements(branch: string): Promise<UpdateCheckResult> {
        try {
            const { stdout } = await execAsync(`git diff HEAD..origin/${branch} --name-only`);
            return {
                needsInstall: stdout.includes("package.json"),
                needsMigrations: stdout.includes("schema.ts") || stdout.includes("drizzle/")
            };
        } catch (e) {
            console.error("Failed to check update requirements:", e);
            return {
                needsInstall: false,
                needsMigrations: false,
                error: e instanceof Error ? e : new Error(String(e))
            };
        }
    }

    static async installDependencies(): Promise<string> {
        const { stdout } = await execAsync("bun install");
        return stdout;
    }

    static async prepareRestartContext(context: RestartContext): Promise<void> {
        await writeFile(this.CONTEXT_FILE, JSON.stringify(context));
    }

    static async triggerRestart(): Promise<void> {
        if (process.env.RESTART_COMMAND) {
            // Run without awaiting - it may kill the process immediately
            exec(process.env.RESTART_COMMAND).unref();
        } else {
            // Fallback: exit the process and let Docker/PM2/systemd restart it
            // Small delay to allow any pending I/O to complete
            setTimeout(() => process.exit(0), 100);
        }
    }

    static async handlePostRestart(client: Client): Promise<void> {
        try {
            const context = await this.loadRestartContext();
            if (!context) return;

            if (this.isContextStale(context)) {
                await this.cleanupContext();
                return;
            }

            const channel = await this.fetchNotificationChannel(client, context.channelId);
            if (!channel) {
                await this.cleanupContext();
                return;
            }

            const result = await this.executePostRestartTasks(context, channel);
            await this.notifyPostRestartResult(channel, result);
            await this.cleanupContext();
        } catch (e) {
            console.error("Failed to handle post-restart context:", e);
        }
    }

    // --- Private Helper Methods ---

    private static async loadRestartContext(): Promise<RestartContext | null> {
        try {
            const contextData = await readFile(this.CONTEXT_FILE, "utf-8");
            return JSON.parse(contextData) as RestartContext;
        } catch {
            return null;
        }
    }

    private static isContextStale(context: RestartContext): boolean {
        return Date.now() - context.timestamp > STALE_CONTEXT_MS;
    }

    private static async fetchNotificationChannel(client: Client, channelId: string): Promise<TextChannel | null> {
        try {
            const channel = await client.channels.fetch(channelId);
            if (channel && channel.isSendable() && channel instanceof TextChannel) {
                return channel;
            }
            return null;
        } catch {
            return null;
        }
    }

    private static async executePostRestartTasks(
        context: RestartContext,
        channel: TextChannel
    ): Promise<PostRestartResult> {
        const result: PostRestartResult = {
            installSuccess: true,
            installOutput: "",
            migrationSuccess: true,
            migrationOutput: "",
            ranInstall: context.installDependencies,
            ranMigrations: context.runMigrations
        };

        // 1. Install Dependencies if needed
        if (context.installDependencies) {
            try {
                await channel.send({ embeds: [getInstallingDependenciesEmbed()] });
                const { stdout } = await execAsync("bun install");
                result.installOutput = stdout;
            } catch (err: unknown) {
                result.installSuccess = false;
                result.installOutput = err instanceof Error ? err.message : String(err);
                console.error("Dependency Install Failed:", err);
            }
        }

        // 2. Run Migrations
        if (context.runMigrations) {
            try {
                const { stdout } = await execAsync("bun x drizzle-kit migrate");
                result.migrationOutput = stdout;
            } catch (err: unknown) {
                result.migrationSuccess = false;
                result.migrationOutput = err instanceof Error ? err.message : String(err);
                console.error("Migration Failed:", err);
            }
        }

        return result;
    }

    private static async notifyPostRestartResult(channel: TextChannel, result: PostRestartResult): Promise<void> {
        await channel.send({ embeds: [getPostRestartEmbed(result)] });
    }

    private static async cleanupContext(): Promise<void> {
        try {
            await unlink(this.CONTEXT_FILE);
        } catch {
            // File may not exist, ignore
        }
    }
}
