import { exec } from "child_process";
import { promisify } from "util";
import { writeFile, readFile, unlink } from "fs/promises";
import { Client, TextChannel } from "discord.js";
import { getPostRestartEmbed, getPostRestartProgressEmbed, type PostRestartProgress } from "@/modules/admin/update.view";
import type { PostRestartResult } from "@/modules/admin/update.view";
import type { RestartContext, UpdateCheckResult, UpdateInfo, CommitInfo } from "@/modules/admin/update.types";

const execAsync = promisify(exec);

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



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

    /**
     * Check for available updates with detailed commit information
     */
    static async checkForUpdates(): Promise<UpdateInfo> {
        const { stdout: branchName } = await execAsync("git rev-parse --abbrev-ref HEAD");
        const branch = branchName.trim();

        const { stdout: currentCommit } = await execAsync("git rev-parse --short HEAD");

        await execAsync("git fetch --all");

        const { stdout: latestCommit } = await execAsync(`git rev-parse --short origin/${branch}`);

        // Get commit log with author info
        const { stdout: logOutput } = await execAsync(
            `git log HEAD..origin/${branch} --format="%h|%s|%an" --no-merges`
        );

        const commits: CommitInfo[] = logOutput
            .trim()
            .split("\n")
            .filter(line => line.length > 0)
            .map(line => {
                const [hash, message, author] = line.split("|");
                return { hash: hash || "", message: message || "", author: author || "" };
            });

        return {
            hasUpdates: commits.length > 0,
            branch,
            currentCommit: currentCommit.trim(),
            latestCommit: latestCommit.trim(),
            commitCount: commits.length,
            commits
        };
    }

    /**
     * Analyze what the update requires
     */
    static async checkUpdateRequirements(branch: string): Promise<UpdateCheckResult> {
        try {
            const { stdout } = await execAsync(`git diff HEAD..origin/${branch} --name-only`);
            const changedFiles = stdout.trim().split("\n").filter(f => f.length > 0);

            const needsRootInstall = changedFiles.some(file =>
                file === "package.json" || file === "bun.lock"
            );

            const needsWebInstall = changedFiles.some(file =>
                file === "web/package.json" || file === "web/bun.lock"
            );

            // Detect if web source files changed (requires rebuild)
            const needsWebBuild = changedFiles.some(file =>
                file.startsWith("web/src/") ||
                file === "web/build.ts" ||
                file === "web/tailwind.config.ts" ||
                file === "web/tsconfig.json"
            );

            const needsMigrations = changedFiles.some(file =>
                file.includes("schema.ts") || file.startsWith("drizzle/")
            );

            return {
                needsRootInstall,
                needsWebInstall,
                needsWebBuild,
                needsMigrations,
                changedFiles
            };
        } catch (e) {
            console.error("Failed to check update requirements:", e);
            return {
                needsRootInstall: false,
                needsWebInstall: false,
                needsWebBuild: false,
                needsMigrations: false,
                changedFiles: [],
                error: e instanceof Error ? e : new Error(String(e))
            };
        }
    }

    /**
     * Get a summary of changed file categories
     */
    static categorizeChanges(changedFiles: string[]): Record<string, number> {
        const categories: Record<string, number> = {};

        for (const file of changedFiles) {
            let category = "Other";

            if (file.startsWith("bot/commands/")) category = "Commands";
            else if (file.startsWith("bot/modules/")) category = "Modules";
            else if (file.startsWith("web/")) category = "Web Dashboard";
            else if (file.startsWith("bot/lib/") || file.startsWith("shared/lib/")) category = "Library";
            else if (file.startsWith("drizzle/") || file.includes("schema")) category = "Database";
            else if (file.endsWith(".test.ts")) category = "Tests";
            else if (file.includes("package.json") || file.includes("lock")) category = "Dependencies";

            categories[category] = (categories[category] || 0) + 1;
        }

        return categories;
    }

    /**
     * Save the current commit for potential rollback
     */
    static async saveRollbackPoint(): Promise<string> {
        const { stdout } = await execAsync("git rev-parse HEAD");
        const commit = stdout.trim();
        await writeFile(this.ROLLBACK_FILE, commit);
        return commit;
    }

    /**
     * Rollback to the previous commit
     */
    static async rollback(): Promise<{ success: boolean; message: string }> {
        try {
            const rollbackCommit = await readFile(this.ROLLBACK_FILE, "utf-8");
            await execAsync(`git reset --hard ${rollbackCommit.trim()}`);
            await unlink(this.ROLLBACK_FILE);
            return { success: true, message: `Rolled back to ${rollbackCommit.trim().substring(0, 7)}` };
        } catch (e) {
            return {
                success: false,
                message: e instanceof Error ? e.message : "No rollback point available"
            };
        }
    }

    /**
     * Check if a rollback point exists
     */
    static async hasRollbackPoint(): Promise<boolean> {
        try {
            await readFile(this.ROLLBACK_FILE, "utf-8");
            return true;
        } catch {
            return false;
        }
    }

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

    /**
     * Install dependencies for specified projects
     */
    static async installDependencies(options: { root: boolean; web: boolean }): Promise<string> {
        const outputs: string[] = [];

        if (options.root) {
            const { stdout } = await execAsync("bun install");
            outputs.push(`📦 Root: ${stdout.trim() || "Done"}`);
        }

        if (options.web) {
            const { stdout } = await execAsync("cd web && bun install");
            outputs.push(`🌐 Web: ${stdout.trim() || "Done"}`);
        }

        return outputs.join("\n");
    }

    /**
     * Prepare restart context with rollback info
     */
    static async prepareRestartContext(context: RestartContext): Promise<void> {
        await writeFile(this.CONTEXT_FILE, JSON.stringify(context));
    }

    /**
     * Trigger a restart
     */
    static async triggerRestart(): Promise<void> {
        if (process.env.RESTART_COMMAND) {
            exec(process.env.RESTART_COMMAND).unref();
        } else {
            setTimeout(() => process.exit(0), 100);
        }
    }

    /**
     * Handle post-restart tasks
     */
    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, context);
            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: "",
            webBuildSuccess: true,
            webBuildOutput: "",
            migrationSuccess: true,
            migrationOutput: "",
            ranInstall: context.installDependencies,
            ranWebBuild: context.buildWebAssets,
            ranMigrations: context.runMigrations,
            previousCommit: context.previousCommit,
            newCommit: context.newCommit
        };

        // Track progress for consolidated message
        const progress: PostRestartProgress = {
            installDeps: context.installDependencies,
            buildWeb: context.buildWebAssets,
            runMigrations: context.runMigrations,
            currentStep: "starting"
        };

        // Only send progress message if there are tasks to run
        const hasTasks = context.installDependencies || context.buildWebAssets || context.runMigrations;
        let progressMessage = hasTasks
            ? await channel.send({ embeds: [getPostRestartProgressEmbed(progress)] })
            : null;

        // Helper to update progress message
        const updateProgress = async () => {
            if (progressMessage) {
                await progressMessage.edit({ embeds: [getPostRestartProgressEmbed(progress)] });
            }
        };

        // 1. Install Dependencies if needed
        if (context.installDependencies) {
            try {
                progress.currentStep = "install";
                await updateProgress();

                const { stdout: rootOutput } = await execAsync("bun install");
                const { stdout: webOutput } = await execAsync("cd web && bun install");

                result.installOutput = `📦 Root: ${rootOutput.trim() || "Done"}\n🌐 Web: ${webOutput.trim() || "Done"}`;
                progress.installDone = true;
            } catch (err: unknown) {
                result.installSuccess = false;
                result.installOutput = err instanceof Error ? err.message : String(err);
                progress.installDone = true; // Mark as done even on failure
                console.error("Dependency Install Failed:", err);
            }
        }

        // 2. Build Web Assets if needed
        if (context.buildWebAssets) {
            try {
                progress.currentStep = "build";
                await updateProgress();

                const { stdout } = await execAsync("cd web && bun run build");
                result.webBuildOutput = stdout.trim() || "Build completed successfully";
                progress.buildDone = true;
            } catch (err: unknown) {
                result.webBuildSuccess = false;
                result.webBuildOutput = err instanceof Error ? err.message : String(err);
                progress.buildDone = true;
                console.error("Web Build Failed:", err);
            }
        }

        // 3. Run Migrations
        if (context.runMigrations) {
            try {
                progress.currentStep = "migrate";
                await updateProgress();

                const { stdout } = await execAsync("bun x drizzle-kit migrate");
                result.migrationOutput = stdout;
                progress.migrateDone = true;
            } catch (err: unknown) {
                result.migrationSuccess = false;
                result.migrationOutput = err instanceof Error ? err.message : String(err);
                progress.migrateDone = true;
                console.error("Migration Failed:", err);
            }
        }

        // Delete progress message before final result
        if (progressMessage) {
            try {
                await progressMessage.delete();
            } catch {
                // Message may already be deleted, ignore
            }
        }

        return result;
    }

    private static async notifyPostRestartResult(
        channel: TextChannel,
        result: PostRestartResult,
        context: RestartContext
    ): Promise<void> {
        const hasRollback = await this.hasRollbackPoint();
        await channel.send(getPostRestartEmbed(result, hasRollback));
    }

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