import { is, optimizer, platform } from "@electron-toolkit/utils";
import Sentry from "@sentry/electron/main";
import {
  app,
  BrowserWindow,
  globalShortcut,
  ipcMain,
  Menu,
  nativeImage,
  shell,
  Tray,
} from "electron";
import log from "electron-log/main";
import { autoUpdater } from "electron-updater";
import path, { join } from "path";
import appIcon from "../../resources/icon.png?asset";
import {
  default as ErrorTypeCheck,
  default as errorTypeCheck,
} from "../util/errorTypeCheck";
import ipcTypes from "../util/ipcTypes.json";
import ImportJob from "./decoder/decoder";
import LocalServer from "./http-server/http-server";
import store from "./store/store";
import { checkForAppUpdates } from "./util/checkForAppUpdates";
import { getMainWindow } from "./util/toRenderer";
import { GetAllEnvFiles } from "./watcher/watcher";
import {
  isKeepAliveAgentInstalled,
  setupKeepAliveAgent,
} from "./setup-keep-alive-agent";
import {
  isKeepAliveTaskInstalled,
  setupKeepAliveTask,
} from "./setup-keep-alive-task";

Sentry.init({
  dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296",
});

log.initialize();
const isMac: boolean = process.platform === "darwin";
const protocol: string = "imexmedia";
let isAppQuitting = false; //Needed on Mac as an override to allow us to fully quit the app.
let isKeepAliveLaunch = false; // Track if launched via keep-alive
// Initialize the server
const localServer = new LocalServer();
const gotTheLock = app.requestSingleInstanceLock();

function createWindow(): void {
  // Create the browser window.
  const { width, height, x, y } = store.get("app.windowBounds") as {
    width: number;
    height: number;
    x: number | undefined;
    y: number | undefined;
  };

  const mainWindow = new BrowserWindow({
    width,
    height,
    x,
    y,
    show: false, // Start hidden, show later if not keep-alive
    minWidth: 600,
    minHeight: 400,
    //autoHideMenuBar: true,
    ...(process.platform === "linux" ? { icon: appIcon } : {}),
    title: "Shop Partner",
    webPreferences: {
      preload: join(__dirname, "../preload/index.js"),
      sandbox: false,
      devTools: true,
    },
  });

  const template: Electron.MenuItemConstructorOptions[] = [
    // { role: 'appMenu' }
    // @ts-ignore
    ...(isMac
        ? [
          {
            label: app.name,
            submenu: [
              { role: "about" },
              { type: "separator" },
              { role: "services" },
              { type: "separator" },
              { role: "hide" },
              { role: "hideOthers" },
              { role: "unhide" },
              { type: "separator" },
              { role: "quit" },
            ],
          },
        ]
        : []),
    // { role: 'fileMenu' }
    {
      label: "File",
      submenu: [
        // @ts-ignore
        ...(!isMac ? [{ role: "about" }] : []),
        // @ts-ignore
        isMac ? { role: "close" } : { role: "quit" },
      ],
    },
    // { role: 'editMenu' }
    {
      label: "Edit",
      submenu: [
        { role: "undo" },
        { role: "redo" },
        { type: "separator" },
        { role: "cut" },
        { role: "copy" },
        { role: "paste" },
        // @ts-ignore
        ...(isMac
            ? [
              { role: "pasteAndMatchStyle" },
              { role: "delete" },
              { role: "selectAll" },
              { type: "separator" },
              {
                label: "Speech",
                submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
              },
            ]
            : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
      ],
    },
    // { role: 'viewMenu' }
    {
      label: "View",
      // @ts-ignore
      submenu: [
        { role: "reload" },
        { role: "forceReload" },
        { role: "toggleDevTools" },
        { type: "separator" },
        { role: "resetZoom" },
        { role: "zoomIn" },
        { role: "zoomOut" },
        { type: "separator" },
        { role: "togglefullscreen" },
      ],
    },
    {
      label: "Application",
      // @ts-ignore
      submenu: [
        {
          label: "Open on Startup",
          checked: store.get("app.openOnStartup") as boolean,
          type: "checkbox",
          click: (): void => {
            const currentSetting = store.get("app.openOnStartup") as boolean;
            store.set("app.openOnStartup", !currentSetting);
            log.info("Open on startup set to", !currentSetting);
            if (!import.meta.env.DEV) {
              app.setLoginItemSettings({
                enabled: true, //This is a windows only command. Updates the task manager and registry.
                openAtLogin: !currentSetting,
              });
            }
          },
        },
        {
          label: `Check for Updates (${app.getVersion()})`,
          click: (): void => {
            checkForAppUpdates();
          },
        },
        {
          label: "Development",
          id: "development",
          visible: import.meta.env.DEV,
          submenu: [
            {
              label: "Connect to Test",
              checked: store.get("app.isTest") as boolean,
              type: "checkbox",
              id: "toggleTest",
              click: (): void => {
                const currentSetting = store.get("app.isTest") as boolean;
                store.set("app.isTest", !currentSetting);
                log.info("Setting isTest to: ", !currentSetting);
                app.relaunch(); // Relaunch the app
                preQuitMethods(); //Quitting handlers aren't called. Manually execute to clean up the app.
                app.exit(0); // Exit the current instance
              },
            },
            {
              label: "Check for updates",
              click: (): void => {
                checkForAppUpdates();
              },
            },
            {
              label: "Open Log File",
              click: (): void => {
                /* action for item 1 */
                shell
                    .openPath(log.transports.file.getFile().path)
                    .catch((error) => {
                      log.error(
                          "Failed to open log file:",
                          errorTypeCheck(error),
                      );
                    });
              },
            },
            {
              label: "Clear Log",
              click: (): void => {
                log.transports.file.getFile().clear();
              },
            },
            {
              label: "Open Config Folder",
              click: (): void => {
                shell.openPath(path.dirname(store.path)).catch((error) => {
                  log.error(
                      "Failed to open config folder:",
                      errorTypeCheck(error),
                  );
                });
              },
            },
            {
              label: "Log the Store",
              click: (): void => {
                log.debug(
                    "Store Contents" + JSON.stringify(store.store, null, 4),
                );
              },
            },
            {
              type: "separator",
            },
            // {
            //   label: "Decode Hardcoded Estimate",
            //   click: (): void => {
            //     ImportJob(`C:\\EMS\\CCC\\9ee762f4.ENV`);
            //   },
            // },
            {
              label: "Install Keep Alive",
              enabled: true, // Default to enabled, update dynamically
              click: async (): Promise<void> => {
                try {
                  if (platform.isWindows) {
                    log.debug("Creating Windows keep-alive task");
                    await setupKeepAliveTask();
                    log.info("Successfully installed Windows keep-alive task");
                  } else if (platform.isMacOS) {
                    log.debug("Creating macOS keep-alive agent");
                    await setupKeepAliveAgent();
                    log.info("Successfully installed macOS keep-alive agent");
                  }
                  // Wait to ensure task/agent is registered
                  await new Promise((resolve) => setTimeout(resolve, 1500));
                  // Rebuild menu and update enabled state
                  await updateKeepAliveMenuItem();
                } catch (error) {
                  log.error(
                      `Failed to install keep-alive: ${error instanceof Error ? error.message : String(error)}`,
                  );
                  // Optionally notify user (e.g., via dialog or log)
                }
              },
            },
            {
              label: "Add All Estimates in watched directories",
              click: (): void => {
                GetAllEnvFiles().forEach((file) => ImportJob(file));
              },
            },
          ],
        },
      ],
    },
    // { role: 'windowMenu' }
    {
      label: "Window",
      submenu: [
        { role: "minimize" },
        { role: "zoom" },
        // @ts-ignore
        ...(isMac
            ? [
              { type: "separator" },
              { role: "front" },
              { type: "separator" },
              { role: "window" },
            ]
            : [{ role: "close" }]),
      ],
    },
  ];

  // Dynamically update Install Keep Alive enabled state
  const updateKeepAliveMenuItem = async (): Promise<void> => {
    try {
      const isInstalled = platform.isWindows
          ? await isKeepAliveTaskInstalled()
          : platform.isMacOS
              ? await isKeepAliveAgentInstalled()
              : false;
      const developmentMenu = template
          .find((item) => item.label === "Application")
          // @ts-ignore
          ?.submenu?.find((item: { id: string }) => item.id === "development")
          ?.submenu as Electron.MenuItemConstructorOptions[];
      const keepAliveItem = developmentMenu?.find(
          (item) => item.label === "Install Keep Alive",
      );
      if (keepAliveItem) {
        keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed
        const menu = Menu.buildFromTemplate(template);
        Menu.setApplicationMenu(menu);
        log.debug(
            `Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`,
        );
      }
    } catch (error) {
      log.error(
          `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  };

  const menu: Electron.Menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
  // Update menu item enabled state on app start
  updateKeepAliveMenuItem().catch((error) => {
    log.error(
        `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
    );
  });

  // Register a global shortcut to show the hidden item
  globalShortcut.register("CommandOrControl+Shift+T", () => {
    console.log("Shortcut pressed! Revealing hidden item.");

    // Update the menu to make the hidden item visible
    // Find the menu item dynamically by its id
    const fileMenu = template.find((item) => item.label === "Application");
    // @ts-ignore
    const hiddenItem = fileMenu?.submenu?.find(
        (item: { id: string }) => item.id === "development",
    );
    //Adjust the development menu as well.

    if (hiddenItem) {
      hiddenItem.visible = true; // Update the visibility dynamically
      const menu: Electron.Menu = Menu.buildFromTemplate(template);
      Menu.setApplicationMenu(menu);
    }
  });

  // Store window properties for later
  const storeWindowState = (): void => {
    const [width, height] = mainWindow.getSize();
    const [x, y] = mainWindow.getPosition();
    store.set("app.windowBounds", { width, height, x, y });
  };
  mainWindow.on("resized", storeWindowState);
  mainWindow.on("maximize", storeWindowState);
  mainWindow.on("unmaximize", storeWindowState);
  mainWindow.on("moved", storeWindowState);

  mainWindow.on("ready-to-show", () => {
    if (!isKeepAliveLaunch) {
      mainWindow.show(); // Show only if not a keep-alive launch
    }
    //Start the HTTP server.
    // Start the local HTTP server
    try {
      localServer.start();
    } catch (error) {
      log.error("Failed to start HTTP server:", errorTypeCheck(error));
    }
  });

  mainWindow.on("close", (event: Electron.Event) => {
    if (!isAppQuitting) {
      event.preventDefault(); // Prevent the window from closing
      mainWindow.hide();
    }
  });

  mainWindow.webContents.setWindowOpenHandler((details) => {
    shell.openExternal(details.url).catch((error) => {
      log.error("Failed to open external URL:", errorTypeCheck(error));
    });
    return { action: "deny" };
  });

  // HMR for renderer base on electron-vite cli.
  // Load the remote URL for development or the local html file for production.
  if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
    mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch((error) => {
      log.error("Failed to load URL:", errorTypeCheck(error));
    });
  } else {
    mainWindow
        .loadFile(join(__dirname, "../renderer/index.html"))
        .catch((error) => {
          log.error("Failed to load file:", errorTypeCheck(error));
        });
  }
  if (import.meta.env.DEV) {
    mainWindow.webContents.openDevTools();
  }
}

if (!gotTheLock) {
  app.quit(); // Quit the app if another instance is already running
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
  // Default open or close DevTools by F12 in development
  // and ignore CommandOrControl + R in production.
  // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
  if (platform.isWindows) {
    app.setAppUserModelId("Shop Partner");
  }

  app.on("browser-window-created", (_, window) => {
    optimizer.watchWindowShortcuts(window);
  });

  let isDefaultProtocolClient: boolean;

  // remove so we can register each time as we run the app.
  app.removeAsDefaultProtocolClient(protocol);

  // If we are running a non-packaged version of the app && on windows
  if (process.env.NODE_ENV === "development" && process.platform === "win32") {
    // Set the path of electron.exe and your app.
    // These two additional parameters are only available on windows.
    isDefaultProtocolClient = app.setAsDefaultProtocolClient(
        protocol,
        process.execPath,
        [path.resolve(process.argv[1])],
    );
  } else {
    isDefaultProtocolClient = app.setAsDefaultProtocolClient(protocol);
  }
  if (isDefaultProtocolClient) {
    log.info("Protocol handler registered successfully.");
  } else {
    log.warn("Failed to register protocol handler.");
  }

  // Add this event handler for second instance
  app.on("second-instance", (_event: Electron.Event, argv: string[]) => {
    const url = argv.find((arg) => arg.startsWith(`${protocol}://`));
    if (url) {
      if (url.startsWith(`${protocol}://keep-alive`)) {
        log.info("Keep-alive protocol received, app is already running.");
        // Do nothing if already running
        return; // Skip openMainWindow to avoid focusing the window
      } else {
        openInExplorer(url);
        openMainWindow(); // Focus window for non-keep-alive URLs
      }
    } else {
      openMainWindow(); // Focus window if no URL
    }
  });

  //Dynamically load ipcMain handlers once ready.
  try {
    const module = await import("./ipc/ipcMainConfig");
    log.debug("Successfully loaded ipcMainConfig");

    // Initialize cron tasks after loading ipcMainConfig
    await module.initializeCronTasks();
    log.info("Cron tasks initialized successfully");
  } catch (error) {
    log.error("Failed to load ipcMainConfig or initialize cron tasks", {
      ...ErrorTypeCheck(error),
    });
  }

  //Create Tray
  const trayicon = nativeImage.createFromPath(appIcon);
  const tray = new Tray(trayicon.resize({ width: 16 }));
  const contextMenu = Menu.buildFromTemplate([
    {
      label: "Show App",
      click: (): void => {
        openMainWindow();
      },
    },
    {
      label: "Quit",
      click: (): void => {
        app.quit(); // actually quit the app.
      },
    },
  ]);

  tray.on("double-click", () => {
    openMainWindow();
  });

  tray.setContextMenu(contextMenu);

  //Check for app updates.
  autoUpdater.logger = log;

  // if (import.meta.env.DEV) {
  //   // Useful for some dev/debugging tasks, but download can
  //   // not be validated because dev app is not signed
  //   autoUpdater.channel = "alpha";
  //   autoUpdater.updateConfigPath = path.join(
  //     __dirname,
  //     "../../dev-app-update.yml",
  //   );
  //   autoUpdater.forceDevUpdateConfig = true;
  //   //autoUpdater.autoDownload = false;
  // }

  autoUpdater.on("checking-for-update", () => {
    log.info("Checking for update...");
    const mainWindow = BrowserWindow.getAllWindows()[0];
    mainWindow?.webContents.send(ipcTypes.toRenderer.updates.checking);
  });
  autoUpdater.on("update-available", (info) => {
    log.info("Update available.", info);
    const mainWindow = BrowserWindow.getAllWindows()[0];
    mainWindow?.webContents.send(ipcTypes.toRenderer.updates.available, info);
  });
  autoUpdater.on("download-progress", (progress) => {
    log.info(`Download speed: ${progress.bytesPerSecond}`);
    log.info(`Downloaded ${progress.percent}%`);
    log.info(`Total downloaded ${progress.transferred}/${progress.total}`);
    const mainWindow = BrowserWindow.getAllWindows()[0];
    mainWindow?.webContents.send(
        ipcTypes.toRenderer.updates.downloading,
        progress,
    );
  });
  autoUpdater.on("update-downloaded", (info) => {
    log.info("Update downloaded", info);
    const mainWindow = BrowserWindow.getAllWindows()[0];
    mainWindow?.webContents.send(ipcTypes.toRenderer.updates.downloaded, info);
  });

  // Check if launched with keep-alive protocol (Windows)
  const args = process.argv.slice(1);
  if (args.some((arg) => arg.startsWith(`${protocol}://keep-alive`))) {
    isKeepAliveLaunch = true;
  }

  //The update itself will run when the bodyshop record is queried to know what release channel to use.
  createWindow();

  app.on("activate", function () {
    openMainWindow();
  });
});

app.on("open-url", (event: Electron.Event, url: string) => {
  event.preventDefault();
  //Don't do anything for now. We just want to open the app.
  if (url.startsWith(`${protocol}://keep-alive`)) {
    log.info("Keep-alive protocol received.");
    if (BrowserWindow.getAllWindows().length === 0) {
      isKeepAliveLaunch = true;
      openMainWindow(); // Launch app if not running
    }
    // Do nothing if already running
    return; // Skip openMainWindow to avoid focusing the window
  } else {
    openInExplorer(url);
    openMainWindow(); // Focus window for non-keep-alive URLs
  }
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit(); //Disable the quit.
  }
});

app.on("before-quit", (props) => {
  console.log(props);
  preQuitMethods();
});

//We need to hit the prequit methods from here as well to ensure the app quits and restarts.
ipcMain.on(ipcTypes.toMain.updates.apply, () => {
  log.info("Applying update from renderer.");
  preQuitMethods();
  setImmediate(() => {
    app.removeAllListeners("window-all-closed");
    const mainWindow = getMainWindow();
    if (mainWindow) mainWindow.close();
    autoUpdater.quitAndInstall(false);
  });
});

function preQuitMethods(): void {
  localServer.stop();
  const currentSetting = store.get("app.openOnStartup") as boolean;
  if (!import.meta.env.DEV) {
    app.setLoginItemSettings({
      enabled: true, //This is a windows only command. Updates the task manager and registry.
      openAtLogin: !currentSetting,
    });
  }
  globalShortcut.unregisterAll();
  isAppQuitting = true;
}

function openMainWindow(): void {
  const mainWindow = BrowserWindow.getAllWindows()[0];
  if (mainWindow) {
    mainWindow.show();
  } else {
    createWindow();
  }
}

function openInExplorer(url: string): void {
  const folderPath: string = decodeURIComponent(url.split(`${protocol}://`)[1]);
  log.info("Opening folder in explorer", folderPath);
  shell.openPath(folderPath).catch((error) => {
    log.error("Failed to open folder in explorer:", errorTypeCheck(error));
  });
}