import Router from "@koa/router";
import { hasShape, predicates } from "@sealcode/ts-predicates";
import { spawn } from "child_process";
import Koa from "koa";
import mount from "koa-mount";
import serve from "koa-static";
import qs from "qs";
import { Readable } from "stream";
import { v4 as uuid } from "uuid";
import { DOCKER_ARGS, IMAGE_NAME, VOLUME_MOUNT } from "./docker-args";
import { requests } from "./memory";
import ScreenshotRequest from "./request";

const router = new Router();

// response
const app = new Koa();
const Static = new Koa();
Static.use(serve("./static"));

app.use(mount("/static", Static));

function attach(docker_id: string, output_stream: Readable) {
  // to prevent browser timeout
  const interval = setInterval(() => output_stream.push("<span></span>"), 500);
  const task = spawn("docker", ["logs", "-f", docker_id]);
  task.stdout.on("data", (d: Buffer) => {
    output_stream.push(d);
    console.log("DATA!", d.toString());
  });
  task.stderr.on("data", (d: Buffer) => {
    /* output_stream.push(d); */
    console.log("STDERR!", d.toString());
  });
  task.stdout.on("error", (d) => {
    output_stream.push(d);
  });
  task.on("close", () => {
    output_stream.push("</pre>");
    output_stream.push(/* HTML */ `<script>
      clearInterval(window.interval);
    </script>`);
    clearInterval(interval);
    output_stream.push(null);
  });
}

router.get("/", async (ctx) => {
  ctx.body = /* HTML */ `<!DOCTYPE html>
    <html>
      <body>
        <form onsubmit="formSubmit(event)">
          <label for="url_input">URL:</label>
          <input type="text" name="url" id="url_input" />
          <br />
          <label for="domains">Domeny (oddzielone przecinkami):</label>
          <input
            type="text"
            name="domains"
            id="domains"
            style="width: calc(100vw - 30%)"
            value="doubleclick.net,facebook.com"
          />
          <br />
          <input type="submit" />
        </form>
        <code><pre id="output"></pre></code>
        <code><pre id="stdout"></pre></code>
      </body>
      <script>
        async function sleep(time) {
          return new Promise((resolve) => setTimeout(resolve, time));
        }
        async function formSubmit(e) {
          e.preventDefault();
          let response = { status: "sending first request..." };
          const url = \`/api/requests?url=\${url_input.value}&\${domains.value
            .split(",")
            .map((d) => "domains[]=" + d)
            .join("&")}\`;
          const { id } = await (await fetch(url, { method: "post" })).json();
          do {
            response = await (await fetch(\`/api/requests/\${id}\`)).json();
            output.innerHTML = JSON.stringify(response, null, "   ").replace(
              /\\/(static|api)\\/.*(.png|.jpg|all-screenshots|v=[0-9]+)/g,
              '<a href="$&">$&</a>'
            );
            stdout.innerHTML = response.output.replace(
              /\\/(static|api)\\/.*(.png|.jpg|all-screenshots)/g,
              '<a href="$&">$&</a>'
            );
            await sleep(1000);
          } while (response.status !== "finished");
        }
      </script>
    </html>`;
});

router.get("/preview", async (ctx) => {
  const response = new Readable({
    read() {
      /*noop */
    },
  });
  // stream data
  ctx.response.set("content-type", "txt/html");
  ctx.type = "html"; // <-- THIS is the important step!
  ctx.body = response;
  response.push("<!doctype html>");
  const id = uuid();
  response.push(
    `<img id="preview" width="1080" height="608" src="/static/${id}/preview.png?id=0"/><br/>`
  );
  response.push(/* HTML */ `<script>
    window.interval = setInterval(() => (preview.src = preview.src + "0"), 500);
  </script>`);
  const params = qs.parse(ctx.querystring);
  if (!hasShape({ url: predicates.string }, params)) {
    throw new Error("MISSING URL PARAM");
  }
  response.push(`Got request to screenshot ${params.url}<pre>`);
  let docker_id = "";
  if (!params.url) {
    ctx.body = "specify url!";
    return;
  }
  const starter = spawn(
    "docker",
    [
      ...DOCKER_ARGS,
      `{"url": "${params.url}", "third_party_domains": ["hotjar.com", "cookielaw.org", "facebook.com", "gemius.pl"]}`,
      id,
    ],
    { cwd: process.cwd() }
  );
  starter.stdout.on("data", (data: Buffer) => {
    docker_id += data.toString().replace(/\n/g, "");
  });

  starter.on("close", () => {
    response.push("spawned " + docker_id);
    attach(docker_id, ctx.body);
  });
});

router.post("/api/requests", async (ctx) => {
  const params = qs.parse(ctx.querystring);
  if (!hasShape({ url: predicates.string }, params)) {
    ctx.body = "Specify url";
    ctx.status = 422;
    return;
  }
  if (!hasShape({ domains: predicates.array(predicates.string) }, params)) {
    ctx.body = "Specify domains as an array of strings";
    ctx.status = 422;
    return;
  }
  if (!Array.isArray(params.domains)) {
    ctx.body = "'domains' should be an array of strings";
    ctx.status = 422;
    return;
  }
  const request = new ScreenshotRequest(params.url, params.domains);
  ctx.status = 303;
  ctx.redirect(`/api/requests/${request.id}`);
});

router.get("/api/requests/:id", async (ctx) => {
  const request = requests[ctx.params.id];
  if (!request) {
    ctx.status = 404;
    return;
  }
  ctx.body = await request.getJSON();
});

router.get("/api/requests/:id/all-screenshots", async (ctx) => {
  const request = requests[ctx.params.id];
  if (!request || request.status != "finished") {
    ctx.status = 404;
    return;
  }
  ctx.attachment(`${request.url.replace(/\W/g, "_")}_all-screenshots.zip`);
  ctx.response.set("content-type", "application/zip");
  ctx.body = request.getZIP();
});

app.use(router.routes()).use(router.allowedMethods());
const port = 3000;
app.listen(port);
console.log(`server started on port ${port}`);
