import env from "./config/env.js";

import path from "node:path";
import fs from "node:fs";
import readline from "readline";

import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat.js";
import { MethodMap, TripwireConfigFile, UriSet } from "service.js";
dayjs.extend(customParseFormat);

const blockedUriStrings = [
  ".env",
  ".git",
  ".php",
  ".zip",
  ".htaccess",
  "wordpress",
  "wp-admin",
  "wp-config",
  "wp-content",
  "wp-includes",
  "wp-mail",
];

const blockedUriPrefixes = [
  "/.well-known",
  "/cgi-bin",
  "/file.php",
  "/modules",
  "/wp",
  "/wp1",
  "/wp2",
  "/xmlrpc.php",
];

const logFile = fs.createReadStream("./data/access.log");
const rl = readline.createInterface({
  input: logFile,
  crlfDelay: Infinity,
});

const methods: MethodMap = new Map<string, UriSet>();

let recordCount = 0;
let prefixMatchCount = 0;
let stringMatchCount = 0;

rl.on("line", (line) => {
  ++recordCount;
  const re = /([^ ]*) ([^ ]*) ([^ ]*) \[([^\]]*)\] "([^"]*)" ([^ ]*) ([^ ]*)/;
  const matches = line.match(re);
  if (!matches || !matches[4] || !matches[5]) {
    return;
  }
  if (matches) {
    // const timestamp = matches[4];
    // const date = dayjs(timestamp, "DD/MMM/YYYY:HH:mm:ss ZZ").toDate();

    const requestTokens = matches[5].split(" ");
    if (!requestTokens[0] || !requestTokens[1] || !requestTokens[2]) {
      // console.error(matches[1], "malformed request", matches[5]);
      return;
    }

    const request = {
      method: requestTokens[0].trim(),
      uri: requestTokens[1].trim(),
      version: requestTokens[2].trim(),
    };
    if (
      request.method[0] === "\\" ||
      request.uri[0] === "\\" ||
      request.version[0] === "\\"
    ) {
      // console.error(matches[1], "malformed request", matches[5]);
      return;
    }

    for (const text of blockedUriStrings) {
      if (request.uri.includes(text)) {
        ++stringMatchCount;
        return;
      }
    }
    for (const prefix of blockedUriPrefixes) {
      if (request.uri.startsWith(prefix)) {
        ++prefixMatchCount;
        return;
      }
    }

    const method = methods.get(request.method);
    if (method) {
      method.add(request.uri);
    } else {
      methods.set(request.method, new Set<string>([request.uri]));
    }

    // const obj = {
    //   remoteHost: matches[1],
    //   remoteLogName: matches[2],
    //   authUser: matches[3],
    //   date,
    //   request,
    //   status: Number(matches[6]),
    //   bytes: Number(matches[7]) || 0,
    // };
    // console.log(JSON.stringify(obj));
  }
});

rl.on("close", async () => {
  console.log(`${recordCount} records read`);
  console.log(`${prefixMatchCount} prefix matches`);
  console.log(`${stringMatchCount} string matches`);

  const configPath = path.join(env.cache.path, "tripwire");
  await fs.promises.mkdir(configPath, { recursive: true });

  const configuration: TripwireConfigFile = {
    prefixes: blockedUriPrefixes,
    strings: blockedUriStrings,
    methods: [],
  };
  for (const [method, uris] of methods) {
    configuration.methods.push({ name: method, uris: Array.from(uris) });
  }
  const configFile = path.join(configPath, "config.json");
  await fs.promises.writeFile(
    configFile,
    JSON.stringify(configuration, null, 2)
  );
});
