import { NodeSSH } from "node-ssh";
import { readFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { EmbedBuilder } from "discord.js";
import { sendUpdateToAllGuilds } from "./discordBot.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

function loadConfig() {
  return JSON.parse(readFileSync(join(__dirname, "..", "config.json"), "utf-8"));
}

// Server logos for Discord embeds
const serverLogos = {
  minecraft: 'https://gsm.zeasy.dev/minecraft.png',
  factorio: 'https://gsm.zeasy.dev/factorio.png',
  zomboid: 'https://gsm.zeasy.dev/zomboid.png',
  vrising: 'https://gsm.zeasy.dev/vrising.png',
  palworld: 'https://gsm.zeasy.dev/palworld.png',
  terraria: 'https://gsm.zeasy.dev/terraria.png',
  openttd: 'https://gsm.zeasy.dev/openttd.png',
  hytale: 'https://gsm.zeasy.dev/hytale.png'
};

// Steam App IDs
const STEAM_APP_IDS = {
  vrising: 1829350,
  palworld: 2394010,
  zomboid: 380870,
  terraria: 1281930  // tModLoader
};

// SSH connection helper (reuse existing pattern)
const sshConnections = new Map();
const SSH_TIMEOUT = 30000; // Longer timeout for update operations

async function getConnection(host, username = "root") {
  const key = username + "@" + host;

  if (sshConnections.has(key)) {
    const conn = sshConnections.get(key);
    if (conn.isConnected()) return conn;
    sshConnections.delete(key);
  }

  const ssh = new NodeSSH();
  await ssh.connect({
    host,
    username,
    privateKeyPath: "/root/.ssh/id_ed25519",
    readyTimeout: SSH_TIMEOUT
  });
  sshConnections.set(key, ssh);
  return ssh;
}

/**
 * Send Discord notification about server update
 */
async function sendUpdateNotification(server, updateInfo) {
  try {
    const embed = new EmbedBuilder()
      .setTitle(`${server.name} wurde aktualisiert!`)
      .setDescription(updateInfo.message || "Der Server wurde erfolgreich aktualisiert.")
      .setColor(0x00FF00) // Green for success
      .setTimestamp();

    if (serverLogos[server.type]) {
      embed.setThumbnail(serverLogos[server.type]);
    }

    if (updateInfo.oldVersion && updateInfo.newVersion) {
      embed.addFields(
        { name: "Alte Version", value: updateInfo.oldVersion, inline: true },
        { name: "Neue Version", value: updateInfo.newVersion, inline: true }
      );
    }

    await sendUpdateToAllGuilds(embed);
    console.log(`[Update] Discord notification sent for ${server.name}`);
  } catch (err) {
    console.error(`[Update] Failed to send Discord notification:`, err.message);
    // Don't throw - update was successful, just notification failed
  }
}

// =============================================================================
// SteamCMD Helper Functions
// =============================================================================

/**
 * Find SteamCMD executable on the server
 */
async function findSteamCmd(ssh, hints = []) {
  // Try hints first
  for (const hint of hints) {
    const result = await ssh.execCommand(`test -f "${hint}" && echo "${hint}"`);
    if (result.stdout.trim()) {
      return result.stdout.trim();
    }
  }

  // Common locations to check
  const commonPaths = [
    "/usr/games/steamcmd",
    "/home/steam/steamcmd/steamcmd.sh",
    "/home/steam/Steam/steamcmd.sh",
    "$HOME/steamcmd/steamcmd.sh",
    "$HOME/Steam/steamcmd.sh"
  ];

  for (const path of commonPaths) {
    const result = await ssh.execCommand(`test -f ${path} && echo "${path}"`);
    if (result.stdout.trim()) {
      return result.stdout.trim();
    }
  }

  // Try which as last resort
  const whichResult = await ssh.execCommand("which steamcmd 2>/dev/null");
  if (whichResult.stdout.trim()) {
    return whichResult.stdout.trim();
  }

  return null;
}

/**
 * Get the current installed build ID from Steam appmanifest
 */
async function getSteamBuildId(ssh, installDir, appId) {
  const manifestPath = `${installDir}/steamapps/appmanifest_${appId}.acf`;
  const result = await ssh.execCommand(`grep -oP '"buildid"\\s*"\\K[^"]+' ${manifestPath} 2>/dev/null`);
  return result.stdout.trim() || null;
}

/**
 * Get the latest available build ID from Steam
 */
async function getLatestSteamBuildId(ssh, appId, steamcmdPath = "steamcmd") {
  // Query Steam for app info
  const result = await ssh.execCommand(
    `${steamcmdPath} +login anonymous +app_info_update 1 +app_info_print ${appId} +quit 2>/dev/null | grep -A2 '"public"' | grep '"buildid"' | head -1 | grep -oP '"buildid"\\s*"\\K[^"]+'`,
    { execOptions: { timeout: 60000 } }
  );
  return result.stdout.trim() || null;
}

/**
 * Create a generic SteamCMD update handler
 */
function createSteamHandler(config) {
  const { appId, installDir, steamcmdHints = [], gameName } = config;

  return {
    supportsUpdate: true,
    gameName,

    async checkForUpdate(server) {
      const ssh = await getConnection(server.host, server.sshUser);
      const actualInstallDir = installDir || server.workDir;

      console.log(`[Update] Checking for ${gameName} updates...`);

      // Find SteamCMD
      const steamcmdPath = await findSteamCmd(ssh, steamcmdHints);
      if (!steamcmdPath) {
        return { hasUpdate: false, error: "SteamCMD nicht gefunden auf dem Server" };
      }
      console.log(`[Update] Found SteamCMD at: ${steamcmdPath}`);

      // Get current build ID
      const currentBuildId = await getSteamBuildId(ssh, actualInstallDir, appId);
      if (!currentBuildId) {
        return { hasUpdate: false, error: "Konnte aktuelle Version nicht ermitteln (appmanifest nicht gefunden)" };
      }

      // Get latest build ID from Steam
      const latestBuildId = await getLatestSteamBuildId(ssh, appId, steamcmdPath);

      if (!latestBuildId) {
        return { hasUpdate: false, error: "Konnte Steam-Version nicht abrufen" };
      }

      const hasUpdate = currentBuildId !== latestBuildId;

      return {
        hasUpdate,
        currentVersion: currentBuildId,
        newVersion: latestBuildId,
        message: hasUpdate ? "Neues Update verfügbar!" : "Server ist auf dem neuesten Stand"
      };
    },

    async performUpdate(server) {
      const ssh = await getConnection(server.host, server.sshUser);
      const actualInstallDir = installDir || server.workDir;

      // Find SteamCMD
      const steamcmdPath = await findSteamCmd(ssh, steamcmdHints);
      if (!steamcmdPath) {
        throw new Error("SteamCMD nicht gefunden auf dem Server");
      }

      // Get old build ID
      const oldBuildId = await getSteamBuildId(ssh, actualInstallDir, appId);

      console.log(`[Update] Starting ${gameName} update with ${steamcmdPath}...`);

      // Run SteamCMD update
      const updateResult = await ssh.execCommand(
        `${steamcmdPath} +login anonymous +force_install_dir "${actualInstallDir}" +app_update ${appId} validate +quit`,
        { execOptions: { timeout: 600000 } } // 10 min timeout for large updates
      );

      console.log(`[Update] SteamCMD output:`, updateResult.stdout);

      if (updateResult.code !== 0 && !updateResult.stdout.includes("Success")) {
        throw new Error("Update fehlgeschlagen: " + (updateResult.stderr || "Unbekannter Fehler"));
      }

      // Get new build ID
      const newBuildId = await getSteamBuildId(ssh, actualInstallDir, appId);

      return {
        success: true,
        message: "Update erfolgreich! Server kann jetzt gestartet werden.",
        oldVersion: oldBuildId || "unbekannt",
        newVersion: newBuildId || "aktuell"
      };
    }
  };
}

// =============================================================================
// Update Handler Registry
// =============================================================================

const updateHandlers = {
  // Factorio (Docker-based)
  factorio: {
    supportsUpdate: true,
    gameName: "Factorio",

    async checkForUpdate(server) {
      const ssh = await getConnection(server.host, server.sshUser);

      // Get current image ID
      const currentResult = await ssh.execCommand(
        `docker inspect --format='{{.Image}}' ${server.containerName} 2>/dev/null`
      );
      const currentImageId = currentResult.stdout.trim();

      if (!currentImageId) {
        return { hasUpdate: false, error: "Container not found" };
      }

      // Pull latest image and check if different
      console.log("[Update] Checking for Factorio updates...");
      const pullResult = await ssh.execCommand(
        "docker pull factoriotools/factorio:latest 2>&1",
        { execOptions: { timeout: 120000 } }
      );

      // Check if image was updated
      const isUpToDate = pullResult.stdout.includes("Image is up to date") ||
                         pullResult.stdout.includes("Status: Image is up to date");

      if (isUpToDate) {
        return {
          hasUpdate: false,
          currentVersion: "latest",
          message: "Server ist auf dem neuesten Stand"
        };
      }

      // Get new image ID
      const newResult = await ssh.execCommand(
        "docker inspect --format='{{.Id}}' factoriotools/factorio:latest 2>/dev/null"
      );
      const newImageId = newResult.stdout.trim();

      // Compare image IDs
      const hasUpdate = currentImageId !== newImageId && newImageId.length > 0;

      return {
        hasUpdate,
        currentVersion: currentImageId.substring(7, 19),
        newVersion: newImageId.substring(7, 19),
        message: hasUpdate ? "Neues Update verfügbar!" : "Server ist auf dem neuesten Stand"
      };
    },

    async performUpdate(server) {
      const ssh = await getConnection(server.host, server.sshUser);

      // Verify server is stopped
      const statusResult = await ssh.execCommand(
        `docker inspect --format='{{.State.Status}}' ${server.containerName} 2>/dev/null`
      );
      if (statusResult.stdout.trim() === "running") {
        throw new Error("Server muss gestoppt sein um zu updaten");
      }

      // Get current image ID for version comparison
      const oldImageResult = await ssh.execCommand(
        `docker inspect --format='{{.Image}}' ${server.containerName} 2>/dev/null`
      );
      const oldImageId = oldImageResult.stdout.trim().substring(7, 19);

      console.log("[Update] Starting Factorio update...");

      // Pull latest image
      const pullResult = await ssh.execCommand(
        "docker pull factoriotools/factorio:latest 2>&1",
        { execOptions: { timeout: 180000 } }
      );
      console.log("[Update] Pull result:", pullResult.stdout);

      // Get current container config for recreation
      const configResult = await ssh.execCommand(`
        docker inspect ${server.containerName} --format='{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null
      `);

      // Parse environment variables
      const envVars = {};
      configResult.stdout.split('\n').forEach(line => {
        const [key, ...valueParts] = line.split('=');
        if (key && valueParts.length > 0) {
          envVars[key] = valueParts.join('=');
        }
      });

      // Remove old container
      console.log("[Update] Removing old container...");
      await ssh.execCommand(`docker stop ${server.containerName} 2>/dev/null || true`);
      await ssh.execCommand(`docker rm ${server.containerName} 2>/dev/null || true`);

      // Build environment string
      const envString = Object.entries(envVars)
        .filter(([k]) => ['SAVE_NAME', 'LOAD_LATEST_SAVE', 'TZ', 'PUID', 'PGID'].includes(k))
        .map(([k, v]) => `-e ${k}="${v}"`)
        .join(' ');

      // Create new container with updated image
      console.log("[Update] Creating new container with updated image...");
      const createResult = await ssh.execCommand(`
        docker create \
          --name ${server.containerName} \
          -p 34197:34197/udp \
          -p 27015:27015/tcp \
          -v /srv/docker/factorio/data:/factorio \
          ${envString || '-e TZ=Europe/Berlin -e PUID=845 -e PGID=845'} \
          --restart=unless-stopped \
          factoriotools/factorio:latest
      `);

      if (createResult.code !== 0) {
        throw new Error("Container creation failed: " + createResult.stderr);
      }

      // Get new image ID
      const newImageResult = await ssh.execCommand(
        "docker inspect --format='{{.Id}}' factoriotools/factorio:latest 2>/dev/null"
      );
      const newImageId = newImageResult.stdout.trim().substring(7, 19);

      // Clean up old images
      console.log("[Update] Cleaning up old images...");
      await ssh.execCommand("docker image prune -f 2>/dev/null || true");

      return {
        success: true,
        message: "Update erfolgreich! Server kann jetzt gestartet werden.",
        oldVersion: oldImageId,
        newVersion: newImageId
      };
    }
  },

  // V Rising (SteamCMD) - runs as root, steam user has steamcmd
  vrising: createSteamHandler({
    appId: STEAM_APP_IDS.vrising,
    installDir: "/home/steam/vrising",
    steamcmdHints: ["/home/steam/steamcmd/steamcmd.sh", "/home/steam/Steam/steamcmd.sh"],
    gameName: "V Rising"
  }),

  // Palworld (SteamCMD) - runs as root
  palworld: createSteamHandler({
    appId: STEAM_APP_IDS.palworld,
    installDir: "/opt/palworld",
    steamcmdHints: ["/usr/games/steamcmd", "/home/steam/steamcmd/steamcmd.sh"],
    gameName: "Palworld"
  }),

  // Project Zomboid (SteamCMD) - runs as pzuser
  zomboid: createSteamHandler({
    appId: STEAM_APP_IDS.zomboid,
    installDir: "/opt/pzserver",
    steamcmdHints: ["/home/pzuser/steamcmd/steamcmd.sh", "/opt/steamcmd/steamcmd.sh"],
    gameName: "Project Zomboid"
  }),

  // Terraria / tModLoader (SteamCMD) - runs as terraria user
  terraria: createSteamHandler({
    appId: STEAM_APP_IDS.terraria,
    installDir: "/home/terraria/tModLoader",
    steamcmdHints: ["/home/terraria/steamcmd/steamcmd.sh", "/home/terraria/Steam/steamcmd.sh"],
    gameName: "Terraria (tModLoader)"
  }),

  // Minecraft Biohazard - Manual update required (modpack)
  minecraft: {
    supportsUpdate: false, // Modpacks need manual update
    gameName: "Biohazard: Project Genesis"
  },

  // OpenTTD - Manual update required
  openttd: {
    supportsUpdate: false,
    gameName: "OpenTTD"
  },

  // Hytale - Manual update required
  hytale: {
    supportsUpdate: false,
    gameName: "Hytale"
  }
};

// =============================================================================
// Public API
// =============================================================================

/**
 * Check if a server type supports updates
 */
export function supportsUpdate(serverType) {
  return updateHandlers[serverType]?.supportsUpdate === true;
}

/**
 * Get list of all server types that support updates
 */
export function getUpdatableServerTypes() {
  return Object.entries(updateHandlers)
    .filter(([_, handler]) => handler.supportsUpdate)
    .map(([type]) => type);
}

/**
 * Check if an update is available for a server
 */
export async function checkForUpdate(server) {
  const handler = updateHandlers[server.type];

  if (!handler || !handler.supportsUpdate) {
    return {
      hasUpdate: false,
      supported: false,
      message: "Updates für diesen Servertyp nicht unterstützt"
    };
  }

  try {
    const result = await handler.checkForUpdate(server);
    return { ...result, supported: true };
  } catch (err) {
    console.error(`[Update] Check failed for ${server.id}:`, err.message);
    return {
      hasUpdate: false,
      supported: true,
      error: err.message
    };
  }
}

/**
 * Perform update for a server (with automatic Discord notification)
 */
export async function performUpdate(server, sendDiscordNotification = true) {
  const handler = updateHandlers[server.type];

  if (!handler || !handler.supportsUpdate) {
    throw new Error("Updates für diesen Servertyp nicht unterstützt");
  }

  const result = await handler.performUpdate(server);

  // Send Discord notification on successful update
  if (result.success && sendDiscordNotification) {
    await sendUpdateNotification(server, result);
  }

  return result;
}
