import { Logger } from "@gnu-taler/taler-util";
import chokidar from "chokidar";
import express from "express";
import https from "https";
import http from "http";
import { parse } from "node:url";
import WebSocket from "ws";

// import locahostCrt from "./keys/localhost.crt";
// import locahostKey from "./keys/localhost.key";
import storiesHtml from "./stories.html";

import path from "node:path";

const httpServerOptions = {
  // key: locahostKey,
  // cert: locahostCrt,
};

const logger = new Logger("serve.ts");


export async function serve(opts: {
  folder: string;
  port: number;
  source?: string;
  tls?: boolean;
  examplesLocationJs?: string;
  examplesLocationCss?: string;
  onSourceUpdate?: () => Promise<void>;
  appPath?: string;
  appSamplePath?: string;
}): Promise<void> {

  const PATHS = {
    WS: "/ws",
    EXAMPLE: "/examples",
    ROOT: "/",
    APP: opts.appPath ?? "/app",
  };
  

  const app = express();

  app.use(PATHS.APP, express.static(opts.folder));

  const httpServer = http.createServer(app);
  const httpPort = opts.port;
  let httpsServer: typeof httpServer | undefined;
  let httpsPort: number | undefined;
  const servers = [httpServer];
  if (opts.tls) {
    httpsServer = https.createServer(httpServerOptions, app);
    httpsPort = opts.port + 1;
    servers.push(httpsServer);
  }

  logger.info(`Dev server. Endpoints:`);
  logger.info(`  ${PATHS.APP}: where root application can be tested`);
  logger.info(`  ${PATHS.EXAMPLE}: where examples can be found and browse`);
  logger.info(`  ${PATHS.WS}: websocket for live reloading`);

  const wss = new WebSocket.Server({ noServer: true });

  wss.on("connection", function connection(ws) {
    ws.send("welcome");
  });

  servers.forEach(function addWSHandler(server) {
    server.on("upgrade", function upgrade(request, socket, head) {
      const { pathname } = parse(request.url || "");
      if (pathname === PATHS.WS) {
        wss.handleUpgrade(request, socket, head, function done(ws) {
          wss.emit("connection", ws, request);
        });
      } else {
        socket.destroy();
      }
    });
  });

  const sendToAllClients = function (data: {type: string, data: object}): void {
    logger.info(`notifying: ${data.type}`);
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(data));
      }
    });
  };
  const watchingFolder = opts.source ?? opts.folder;
  logger.info(`watching ${watchingFolder} for changes`);

  chokidar.watch(watchingFolder).on("change", (path, stats) => {
    logger.info(`changed: ${path}`);

    if (opts.onSourceUpdate) {
      sendToAllClients({ type: "file-updated-start", data: { path } });
      opts
        .onSourceUpdate()
        .then((result) => {
          sendToAllClients({
            type: "file-updated-done",
            data: { path, result },
          });
        })
        .catch((error) => {
          sendToAllClients({
            type: "file-updated-failed",
            data: { path, error: JSON.stringify(error) },
          });
        });
    } else {
      sendToAllClients({ type: "file-change", data: { path } });
    }
  });

  if (opts.onSourceUpdate) opts.onSourceUpdate();

  app.get(PATHS.EXAMPLE, function (req: any, res: any) {
    res.set("Content-Type", "text/html");
    res.send(
      storiesHtml
        .replace(
          "__EXAMPLES_JS_FILE_LOCATION__",
          opts.examplesLocationJs ?? `.${PATHS.APP}/stories.js`,
        )
        .replace(
          "__EXAMPLES_CSS_FILE_LOCATION__",
          opts.examplesLocationCss ?? `.${PATHS.APP}/stories.css`,
        ),
    );
  });

  app.get(PATHS.ROOT, function (req: any, res: any) {
    res.set("Content-Type", "text/html");
    res.send(`<html>
      <head><title>Development Server</title></head>
      <body>
      it will connect to this server using websocket and reload automatically when the code changes
      <h1>Endpoints</h1>
      <dl>
        <dt><a href=".${opts.appSamplePath ?? "/app"}">app</a></dt>
        <dd>Where you can find the application. Reloads on update.</dd>

        <dt><a href="./examples">ui examples</a></dt>
        <dd>Where you can browse static UI examples. Reloads on update.</dd>

        <dt><a href="./ws">websocket</a></dt>
        <dd>Announce when the code changes</dd>
      </dl>
      </body>
      </html>`);
  });

  logger.info(`Serving ${opts.folder} on http://localhost:${httpPort}/ via plain HTTP`);
  httpServer.listen(httpPort);
  if (httpsServer !== undefined) {
    logger.info(`Serving ${opts.folder} on ${httpsPort}: HTTP + TLS`);
    httpsServer.listen(httpsPort);
  }
}
