/*
 This file is part of GNU Taler
 (C) 2025 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

import { Logger } from "@gnu-taler/taler-util";
import * as http from "node:http";

const logger = new Logger("http-server.ts");

export function splitInTwoAt(s: string, separator: string): [string, string] {
  const idx = s.indexOf(separator);
  if (idx === -1) {
    return [s, ""];
  }
  return [s.slice(0, idx), s.slice(idx + 1)];
}

export function readBodyStr(req: http.IncomingMessage): Promise<string> {
  return new Promise((resolve, reject) => {
    let reqBody = "";
    req.on("data", (x) => {
      reqBody += x;
    });

    req.on("end", () => {
      resolve(reqBody);
    });
  });
}

export function readBodyBytes(req: http.IncomingMessage): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    let chunks: Buffer[] = [];
    req.on("data", (x) => {
      chunks.push(Buffer.from(x));
    });

    req.on("end", () => {
      resolve(new Uint8Array(Buffer.concat(chunks)));
    });
  });
}

export function respondJson(
  resp: http.ServerResponse<http.IncomingMessage>,
  status: number,
  body: any,
): void {
  resp.writeHead(status, { "Content-Type": "application/json" });
  resp.end(JSON.stringify(body));
}

export interface SimpleServerResponse {
  /**
   * HTTP response status.
   */
  status: number;

  /**
   * JSON body.
   */
  body: any;
}

export interface SimpleServerRequest {
  body: any;
  method: string;
}

/**
 * Very simple HTTP server to be used as a mock server for tests.
 */
export class HarnessHttpServer {
  private handlers: {
    path: string;
    handler: (req: SimpleServerRequest) => Promise<SimpleServerResponse>;
  }[] = [];

  private server:
    | http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>
    | undefined;

  /**
   * Add a handler for a hard-coded request path.
   */
  addSimpleJsonHandler(
    path: string,
    handler: (req: SimpleServerRequest) => Promise<any>,
  ): void {
    this.handlers.push({ path, handler });
  }

  async start(port: number): Promise<void> {
    const server = (this.server = http.createServer(async (req, res) => {
      const requestUrl = req.url!;
      logger.info(`harness server: got ${req.method} request, ${requestUrl}`);
      const method = req.method?.toUpperCase();
      if (!method) {
        throw Error("no method");
      }
      let body: any | undefined;
      if (method === "POST" || method === "PUT" || method === "PATCH") {
        body = JSON.parse(await readBodyStr(req));
      }

      const [path, query] = splitInTwoAt(requestUrl, "?");

      for (const handler of this.handlers) {
        if (handler.path === path) {
          const handlerResp = await handler.handler({
            method,
            body,
          });
          respondJson(res, handlerResp.status, handlerResp.body);
          return;
        }
      }
      logger.warn("no handler");
      respondJson(res, 404, {});
    }));

    await new Promise<void>((resolve, reject) => {
      server.listen(port, () => resolve());
    });
  }

  stop() {
    this.server?.close();
  }
}
