import { appendFile, mkdir } from "node:fs/promises";

const port = 3001;
const logDir = "cw-api-logs";
const logFilePath = `${logDir}/test-webserver-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`;

const jsonBodyMethods = ["POST", "PUT", "PATCH", "DELETE"];

type ParsedJson = Record<string, unknown> | unknown[];
type EventSummary = ReturnType<typeof buildSummary>;

const color = {
  reset: "\x1b[0m",
  bold: "\x1b[1m",
  dim: "\x1b[2m",
  cyan: "\x1b[36m",
  blue: "\x1b[34m",
  yellow: "\x1b[33m",
  magenta: "\x1b[35m",
  green: "\x1b[32m",
  gray: "\x1b[90m",
};

const paint = (value: string, tone: keyof typeof color) =>
  `${color[tone]}${value}${color.reset}`;

const safeParseJson = (value: string): ParsedJson | null => {
  try {
    const parsed = JSON.parse(value);
    const isObject = typeof parsed === "object" && parsed !== null;

    return isObject ? (parsed as ParsedJson) : null;
  } catch {
    return null;
  }
};

const parseEntity = (value: unknown): ParsedJson | null => {
  if (typeof value === "string") return safeParseJson(value);
  if (typeof value !== "object" || value === null) return null;

  return value as ParsedJson;
};

const asObject = (value: ParsedJson | null): Record<string, unknown> | null => {
  if (!value) return null;
  if (Array.isArray(value)) return null;

  return value;
};

const parseJsonStringFields = (
  value: Record<string, unknown> | null,
): Record<string, unknown> | null => {
  if (!value) return null;

  return Object.entries(value).reduce<Record<string, unknown>>(
    (acc, [key, current]) => {
      if (typeof current !== "string") {
        acc[key] = current;

        return acc;
      }

      const looksLikeJson = current.startsWith("{") || current.startsWith("[");
      if (!looksLikeJson) {
        acc[key] = current;

        return acc;
      }

      const parsed = safeParseJson(current);
      acc[key] = parsed ?? current;

      return acc;
    },
    {},
  );
};

const parseQuery = (url: URL) => {
  const entries = [...url.searchParams.entries()];
  const params = entries.reduce<Record<string, string>>((acc, [key, value]) => {
    acc[key] = value;

    return acc;
  }, {});
  const rawQuery = url.search.startsWith("?")
    ? url.search.slice(1)
    : url.search;
  const firstSegment = rawQuery.split("&")[0] ?? "";
  const hasEquals = firstSegment.includes("=");
  const inferredId = !hasEquals && firstSegment ? firstSegment : null;

  return {
    params,
    inferredId,
  };
};

const buildSummary = (
  parsedBody: Record<string, unknown> | null,
  parsedEntity: Record<string, unknown> | null,
) => {
  if (!parsedBody) return null;

  return {
    messageId: parsedBody.MessageId ?? null,
    action: parsedBody.Action ?? null,
    type: parsedBody.Type ?? null,
    id: parsedBody.ID ?? null,
    memberId: parsedBody.MemberId ?? null,
    entityStatus:
      parsedEntity?.StatusName ??
      parsedEntity?.TicketStatus ??
      parsedEntity?.Status ??
      null,
    entitySummary: parsedEntity?.Summary ?? parsedEntity?.CompanyName ?? null,
    entityUpdatedBy: parsedEntity?.UpdatedBy ?? null,
    entityLastUpdated:
      parsedEntity?.LastUpdatedUTC ?? parsedEntity?.LastUpdated ?? null,
  };
};

const displayTerminalEvent = (
  method: string,
  routePath: string,
  query: { params: Record<string, string>; inferredId: string | null },
  summary: EventSummary,
  timestamp: string,
) => {
  const id = String(summary?.id ?? query.inferredId ?? "-");
  const action = String(summary?.action ?? query.params.action ?? "-");
  const eventType = String(summary?.type ?? routePath.split("/")[1] ?? "-");
  const actor = String(
    summary?.entityUpdatedBy ??
      query.params.memberId ??
      summary?.memberId ??
      "-",
  );
  const status = String(summary?.entityStatus ?? "-");
  const title = String(summary?.entitySummary ?? "-");

  const methodTone = method === "GET" ? "green" : "yellow";

  console.log();
  console.log(
    `${paint("●", "cyan")} ${paint(method, methodTone)} ${paint(routePath, "blue")} ${paint(timestamp, "gray")}`,
  );
  console.log(
    `${paint("type", "magenta")}: ${paint(eventType, "bold")}  ${paint("action", "magenta")}: ${action}  ${paint("id", "magenta")}: ${id}`,
  );
  console.log(
    `${paint("actor", "magenta")}: ${paint(actor, "cyan")}  ${paint("status", "magenta")}: ${status}`,
  );
  console.log(`${paint("title", "magenta")}: ${title}`);
};

const writeLogRecord = async (record: Record<string, unknown>) => {
  await appendFile(logFilePath, `${JSON.stringify(record)}\n`, "utf8");
};

await mkdir(logDir, { recursive: true });

Bun.serve({
  port,
  async fetch(request) {
    const url = new URL(request.url);
    const routePath = `${url.pathname}${url.search}`;
    const method = request.method;
    const query = parseQuery(url);
    const startedAt = new Date().toISOString();
    const rawBody = jsonBodyMethods.includes(method)
      ? await request.text()
      : "";
    const parsedJson = safeParseJson(rawBody);
    const parsedBody = asObject(parsedJson);
    const parsedBodyExpanded = parseJsonStringFields(parsedBody);
    const parsedEntity = asObject(parseEntity(parsedBodyExpanded?.Entity));
    const summary = buildSummary(parsedBodyExpanded, parsedEntity);

    const responseBody = {
      success: true,
      method,
      path: routePath,
      timestamp: startedAt,
      summary,
    };

    const responseStatus = 200;

    displayTerminalEvent(method, routePath, query, summary, startedAt);

    await writeLogRecord({
      timestamp: startedAt,
      request: {
        method,
        path: routePath,
        query,
        headers: Object.fromEntries(request.headers.entries()),
        bodyRaw: rawBody || null,
        bodyParsed: parsedBodyExpanded,
        entityParsed: parsedEntity,
        summary,
      },
      response: {
        status: responseStatus,
        body: responseBody,
      },
    });

    return Response.json(responseBody, { status: responseStatus });
  },
});

console.log(`Test webserver listening on http://localhost:${port}`);
console.log(`Response/request log file: ${logFilePath}`);
