import { BrowserWindow, ipcMain, shell } from 'electron';
import { createServer, Server } from 'http';
import { IPC } from './shared-with-frontend/ipc-events.const';
import {
  OAUTH_LOOPBACK_PORT_MIN,
  OAUTH_LOOPBACK_PORT_MAX,
} from './shared-with-frontend/oauth-loopback.const';
import { log } from 'electron-log/main';

const LOOPBACK_HOST = '127.0.0.1';
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes

let loopbackServer: Server | null = null;
let oauthTimeoutId: ReturnType<typeof setTimeout> | null = null;

const cleanupServer = (): void => {
  if (oauthTimeoutId) {
    clearTimeout(oauthTimeoutId);
    oauthTimeoutId = null;
  }
  if (loopbackServer) {
    loopbackServer.close();
    loopbackServer = null;
  }
};

// Success page shown in the user's browser after completing OAuth
const SUCCESS_HTML = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Super Productivity</title>
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;
justify-content:center;height:100vh;margin:0;background:#f5f5f5}
.card{text-align:center;padding:2rem;background:#fff;border-radius:8px;
box-shadow:0 2px 8px rgba(0,0,0,.1)}</style></head>
<body><div class="card"><h2>Authentication complete</h2>
<p>You can close this tab and return to Super Productivity.</p></div></body></html>`;

export const initPluginOAuth = (mainWin: BrowserWindow): void => {
  // Prepare: start a loopback HTTP server and return the port.
  // Google Desktop OAuth requires http://127.0.0.1:<port> redirect URIs
  // and blocks embedded webviews, so we open the system browser instead.
  ipcMain.handle(
    IPC.PLUGIN_OAUTH_PREPARE,
    async (_event, requestedPort?: number): Promise<{ port: number }> => {
      cleanupServer();

      return new Promise<{ port: number }>((resolve, reject) => {
        let handled = false;

        let port = 0;
        if (requestedPort !== undefined) {
          if (
            !Number.isInteger(requestedPort) ||
            requestedPort < OAUTH_LOOPBACK_PORT_MIN ||
            requestedPort > OAUTH_LOOPBACK_PORT_MAX
          ) {
            reject(
              new Error(
                `Invalid OAuth loopback port ${requestedPort}; must be an integer in [${OAUTH_LOOPBACK_PORT_MIN}, ${OAUTH_LOOPBACK_PORT_MAX}].`,
              ),
            );
            return;
          }
          port = requestedPort;
        }

        const server = createServer((req, res) => {
          if (handled) {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(SUCCESS_HTML);
            return;
          }
          handled = true;

          const url = new URL(req.url!, `http://${LOOPBACK_HOST}`);
          const code = url.searchParams.get('code');
          const error = url.searchParams.get('error');
          const state = url.searchParams.get('state');

          // eslint-disable-next-line @typescript-eslint/naming-convention
          res.writeHead(200, { 'Content-Type': 'text/html' });
          res.end(SUCCESS_HTML);

          mainWin.webContents.send(IPC.PLUGIN_OAUTH_CB, { code, error, state });

          // Re-focus the main window after auth completes
          if (!mainWin.isDestroyed()) {
            mainWin.show();
            mainWin.focus();
          }

          cleanupServer();
        });

        server.on('error', (err) => {
          cleanupServer();
          // Note: EADDRINUSE message interpolates port which is 0 only in the system-assigned
          // (no requested port) case — where EADDRINUSE effectively cannot occur.
          if ((err as NodeJS.ErrnoException).code === 'EADDRINUSE') {
            reject(
              new Error(
                `OAuth loopback port ${port} is already in use. Close the app using it and try again.`,
              ),
            );
          } else {
            reject(err);
          }
        });

        server.listen(port, LOOPBACK_HOST, () => {
          const addr = server.address();
          if (addr && typeof addr !== 'string') {
            loopbackServer = server;
            oauthTimeoutId = setTimeout(() => {
              log('Plugin OAuth: Timeout – closing abandoned loopback server');
              cleanupServer();
            }, OAUTH_TIMEOUT_MS);
            log(`Plugin OAuth: Loopback server listening on port ${addr.port}`);
            resolve({ port: addr.port });
          } else {
            server.close();
            reject(new Error('Failed to start OAuth loopback server'));
          }
        });
      });
    },
  );

  // Open the auth URL in the system browser (not an embedded webview).
  // Google blocks OAuth in embedded browsers (Electron BrowserWindow).
  ipcMain.on(IPC.PLUGIN_OAUTH_START, (_ev: unknown, { url }: { url: string }) => {
    log('Plugin OAuth: Opening system browser for auth');

    // Validate URL protocol before opening to prevent file:// or javascript: abuse.
    // Echo back the state param so the renderer can match the error to the
    // pending flow (state validation in handleRedirectError).
    let state: string | undefined;
    try {
      const parsed = new URL(url);
      state = parsed.searchParams.get('state') ?? undefined;
      if (parsed.protocol !== 'https:') {
        log('Plugin OAuth: Rejected non-https auth URL:', parsed.protocol);
        mainWin.webContents.send(IPC.PLUGIN_OAUTH_CB, {
          error: 'invalid_auth_url',
          state,
        });
        cleanupServer();
        return;
      }
    } catch {
      mainWin.webContents.send(IPC.PLUGIN_OAUTH_CB, {
        error: 'invalid_auth_url',
        state,
      });
      cleanupServer();
      return;
    }

    shell.openExternal(url).catch((err: unknown) => {
      log('Plugin OAuth: Failed to open system browser:', err);
      mainWin.webContents.send(IPC.PLUGIN_OAUTH_CB, {
        error: 'failed_to_open_browser',
        state,
      });
      cleanupServer();
    });
  });
};
