import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import axios from "axios";
import sudo from "sudo-prompt";
import { app } from "electron";
import {
  HYDRA_DECKY_PLUGIN_LOCATION,
  DECKY_PLUGINS_LOCATION,
} from "@main/constants";
import { logger } from "./logger";
import { SevenZip } from "./7zip";
import { SystemPath } from "./system-path";
import { HydraApi } from "./hydra-api";

interface DeckyReleaseInfo {
  version: string;
  downloadUrl: string;
}

export class DeckyPlugin {
  private static releaseInfo: DeckyReleaseInfo | null = null;

  private static async getDeckyReleaseInfo(): Promise<DeckyReleaseInfo> {
    if (this.releaseInfo) {
      return this.releaseInfo;
    }

    try {
      const response = await HydraApi.get<DeckyReleaseInfo>(
        "/decky/release",
        {},
        { needsAuth: false }
      );

      this.releaseInfo = response;
      return response;
    } catch (error) {
      logger.error("Failed to fetch Decky release info:", error);
      throw error;
    }
  }

  private static getPackageJsonPath(): string {
    return path.join(HYDRA_DECKY_PLUGIN_LOCATION, "package.json");
  }

  private static async downloadPlugin(): Promise<string> {
    logger.log("Downloading Hydra Decky plugin...");

    const releaseInfo = await this.getDeckyReleaseInfo();
    const tempDir = SystemPath.getPath("temp");
    const zipPath = path.join(tempDir, "Hydra.zip");

    const response = await axios.get(releaseInfo.downloadUrl, {
      responseType: "arraybuffer",
    });

    await fs.promises.writeFile(zipPath, response.data);
    logger.log(`Plugin downloaded to: ${zipPath}`);

    return zipPath;
  }

  private static async extractPlugin(zipPath: string): Promise<string> {
    logger.log("Extracting Hydra Decky plugin...");

    const tempDir = SystemPath.getPath("temp");
    const extractPath = path.join(tempDir, "hydra-decky-plugin");

    if (fs.existsSync(extractPath)) {
      await fs.promises.rm(extractPath, { recursive: true, force: true });
    }

    await fs.promises.mkdir(extractPath, { recursive: true });

    try {
      await SevenZip.extractFile({
        filePath: zipPath,
        outputPath: extractPath,
      });
      logger.log(`Plugin extracted to: ${extractPath}`);
      return extractPath;
    } catch {
      throw new Error("Failed to extract plugin");
    }
  }

  private static needsSudo(): boolean {
    try {
      if (fs.existsSync(DECKY_PLUGINS_LOCATION)) {
        fs.accessSync(DECKY_PLUGINS_LOCATION, fs.constants.W_OK);
        return false;
      }

      const parentDir = path.dirname(DECKY_PLUGINS_LOCATION);
      if (fs.existsSync(parentDir)) {
        fs.accessSync(parentDir, fs.constants.W_OK);
        return false;
      }

      return true;
    } catch (error) {
      if (
        error &&
        typeof error === "object" &&
        "code" in error &&
        (error.code === "EACCES" || error.code === "EPERM")
      ) {
        return true;
      }
      throw error;
    }
  }

  private static async installPluginWithSudo(
    extractPath: string
  ): Promise<void> {
    logger.log("Installing plugin with sudo...");

    const username = os.userInfo().username;
    const sourcePath = path.join(extractPath, "Hydra");

    return new Promise((resolve, reject) => {
      const command = `mkdir -p "${DECKY_PLUGINS_LOCATION}" && rm -rf "${HYDRA_DECKY_PLUGIN_LOCATION}" && cp -r "${sourcePath}" "${HYDRA_DECKY_PLUGIN_LOCATION}" && chown -R ${username}: "${DECKY_PLUGINS_LOCATION}"`;

      sudo.exec(
        command,
        { name: app.getName() },
        (sudoError, _stdout, stderr) => {
          if (sudoError) {
            logger.error("Failed to install plugin with sudo:", sudoError);
            reject(sudoError);
          } else {
            logger.log("Plugin installed successfully with sudo");
            if (stderr) {
              logger.log("Sudo stderr:", stderr);
            }
            resolve();
          }
        }
      );
    });
  }

  private static async installPluginWithoutSudo(
    extractPath: string
  ): Promise<void> {
    logger.log("Installing plugin without sudo...");

    const sourcePath = path.join(extractPath, "Hydra");

    if (!fs.existsSync(DECKY_PLUGINS_LOCATION)) {
      await fs.promises.mkdir(DECKY_PLUGINS_LOCATION, { recursive: true });
    }

    if (fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
      await fs.promises.rm(HYDRA_DECKY_PLUGIN_LOCATION, {
        recursive: true,
        force: true,
      });
    }

    await fs.promises.cp(sourcePath, HYDRA_DECKY_PLUGIN_LOCATION, {
      recursive: true,
    });

    logger.log("Plugin installed successfully");
  }

  private static async installPlugin(extractPath: string): Promise<void> {
    if (this.needsSudo()) {
      await this.installPluginWithSudo(extractPath);
    } else {
      await this.installPluginWithoutSudo(extractPath);
    }
  }

  private static async updatePlugin(): Promise<void> {
    let zipPath: string | null = null;
    let extractPath: string | null = null;

    try {
      zipPath = await this.downloadPlugin();
      extractPath = await this.extractPlugin(zipPath);
      await this.installPlugin(extractPath);

      logger.log("Plugin update completed successfully");
    } catch (error) {
      logger.error("Failed to update plugin:", error);
      throw error;
    } finally {
      if (zipPath) {
        try {
          await fs.promises.rm(zipPath, { force: true });
          logger.log("Cleaned up downloaded zip file");
        } catch (cleanupError) {
          logger.error("Failed to clean up zip file:", cleanupError);
        }
      }

      if (extractPath) {
        try {
          await fs.promises.rm(extractPath, { recursive: true, force: true });
          logger.log("Cleaned up extraction directory");
        } catch (cleanupError) {
          logger.error(
            "Failed to clean up extraction directory:",
            cleanupError
          );
        }
      }
    }
  }

  public static async checkAndUpdateIfOutdated(): Promise<void> {
    if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
      logger.log("Hydra Decky plugin not installed, skipping update check");
      return;
    }

    const packageJsonPath = this.getPackageJsonPath();

    try {
      if (!fs.existsSync(packageJsonPath)) {
        logger.log(
          "Hydra Decky plugin package.json not found, skipping update"
        );
        return;
      }

      const releaseInfo = await this.getDeckyReleaseInfo();
      const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
      const packageJson = JSON.parse(packageJsonContent);
      const currentVersion = packageJson.version;
      const isOutdated = currentVersion !== releaseInfo.version;

      if (isOutdated) {
        logger.log(
          `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}. Updating...`
        );

        await this.updatePlugin();
        logger.log("Hydra Decky plugin updated successfully");
      } else {
        logger.log(`Hydra Decky plugin is up to date (${currentVersion})`);
      }
    } catch (error) {
      logger.error(`Error checking/updating Hydra Decky plugin: ${error}`);
    }
  }

  public static async checkPluginVersion(): Promise<{
    exists: boolean;
    outdated: boolean;
    currentVersion: string | null;
    expectedVersion: string;
  }> {
    try {
      const releaseInfo = await this.getDeckyReleaseInfo();

      if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) {
        logger.log("Hydra Decky plugin folder not found, installing...");

        try {
          await this.updatePlugin();

          // Read the actual installed version from package.json
          const packageJsonPath = this.getPackageJsonPath();
          if (fs.existsSync(packageJsonPath)) {
            const packageJsonContent = fs.readFileSync(
              packageJsonPath,
              "utf-8"
            );
            const packageJson = JSON.parse(packageJsonContent);
            return {
              exists: true,
              outdated: false,
              currentVersion: packageJson.version,
              expectedVersion: releaseInfo.version,
            };
          }

          return {
            exists: true,
            outdated: false,
            currentVersion: releaseInfo.version,
            expectedVersion: releaseInfo.version,
          };
        } catch (error) {
          logger.error("Failed to install plugin:", error);
          return {
            exists: false,
            outdated: true,
            currentVersion: null,
            expectedVersion: releaseInfo.version,
          };
        }
      }

      const packageJsonPath = this.getPackageJsonPath();

      try {
        if (!fs.existsSync(packageJsonPath)) {
          logger.log(
            "Hydra Decky plugin package.json not found, installing..."
          );

          await this.updatePlugin();

          // Read the actual installed version from package.json
          if (fs.existsSync(packageJsonPath)) {
            const packageJsonContent = fs.readFileSync(
              packageJsonPath,
              "utf-8"
            );
            const packageJson = JSON.parse(packageJsonContent);
            return {
              exists: true,
              outdated: false,
              currentVersion: packageJson.version,
              expectedVersion: releaseInfo.version,
            };
          }

          return {
            exists: true,
            outdated: false,
            currentVersion: releaseInfo.version,
            expectedVersion: releaseInfo.version,
          };
        }

        const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
        const packageJson = JSON.parse(packageJsonContent);
        const currentVersion = packageJson.version;
        const isOutdated = currentVersion !== releaseInfo.version;

        if (isOutdated) {
          logger.log(
            `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}`
          );

          await this.updatePlugin();

          if (fs.existsSync(packageJsonPath)) {
            const updatedPackageJsonContent = fs.readFileSync(
              packageJsonPath,
              "utf-8"
            );
            const updatedPackageJson = JSON.parse(updatedPackageJsonContent);
            return {
              exists: true,
              outdated: false,
              currentVersion: updatedPackageJson.version,
              expectedVersion: releaseInfo.version,
            };
          }

          return {
            exists: true,
            outdated: false,
            currentVersion: releaseInfo.version,
            expectedVersion: releaseInfo.version,
          };
        } else {
          logger.log(`Hydra Decky plugin is up to date (${currentVersion})`);
        }

        return {
          exists: true,
          outdated: isOutdated,
          currentVersion,
          expectedVersion: releaseInfo.version,
        };
      } catch (error) {
        logger.error(`Error checking Hydra Decky plugin version: ${error}`);
        return {
          exists: false,
          outdated: true,
          currentVersion: null,
          expectedVersion: releaseInfo.version,
        };
      }
    } catch (error) {
      logger.error(`Error fetching release info: ${error}`);
      return {
        exists: false,
        outdated: true,
        currentVersion: null,
        expectedVersion: "unknown",
      };
    }
  }
}
