import {
  blue,
  bold,
  brightWhite,
  dim,
  gray,
  italic,
  reset,
  underline,
  white,
  yellow,
} from "https://deno.land/std@0.171.0/fmt/colors.ts";
import { MessageFormatter } from "https://crux.land/formatter@0.4.0";
import { defaultTheme, highlight } from "https://deno.land/x/stx@0.1.0/mod.ts";
import "https://deno.land/x/stx@0.1.0/components/_all.ts";

export interface VerboseFetchOptions {
  log?: (formatter: string, ...args: unknown[]) => void;
  fmtMethod?: string;
  fmtUrlProtocol?: string;
  fmtUrlColonSlashSlash?: string;
  fmtUrlHostname?: string;
  fmtUrlPortColon?: string;
  fmtUrlPort?: string;
  fmtUrlPathname?: string;
  fmtUrlQueryQuestionmark?: string;
  fmtUrlQueryAmpersand?: string;
  fmtUrlQueryKey?: string;
  fmtUrlQueryEquals?: string;
  fmtUrlQueryValue?: string;
  fmtUrlHashHashtag?: string;
  fmtUrlHash?: string;
  fmtRequestHeaderTitle?: string;
  fmtRequestHeaderName?: string;
  fmtRequestHeaderColon?: string;
  fmtRequestHeaderValue?: string;
  fmtResponseStatus?: string;
  fmtResponseText?: string;
  fmtResponseHeaderTitle?: string;
  fmtResponseHeaderName?: string;
  fmtResponseHeaderColon?: string;
  fmtResponseHeaderValue?: string;
  fmtResponseContentTitle?: string;
  fmtContent?: (content: string, language: string) => string;
}

const style = (text: string, ...styles: ((text: string) => string)[]) =>
  styles.reduce((text, styler) => styler(text), text);

const S = style(" ", reset);

const FMT_METHOD = style("%s", white, bold);
const FMT_URL_PROTOCOL = style("%s", dim, gray, italic);
const FMT_URL_COLON_SLASH_SLASH = style("%s", dim, gray, italic);
const FMT_URL_HOSTNAME = style("%s", brightWhite);
const FMT_URL_PORT_COLON = style("%s", italic, gray);
const FMT_URL_PORT = style("%s", italic, gray);
const FMT_URL_PATHNAME = style("%s", gray, underline);
const FMT_URL_QUERY_QUESTIONMARK = style("%s", dim, gray, bold);
const FMT_URL_QUERY_AMPERSAND = style("%s", dim, gray);
const FMT_URL_QUERY_KEY = style("%s", blue);
const FMT_URL_QUERY_EQUALS = style("%s", dim, gray);
const FMT_URL_QUERY_VALUE = style("%s", gray, italic);
const FMT_URL_HASH_HASHTAG = style("%s", dim, gray, bold);
const FMT_URL_HASH = style("%s", gray, italic);
const FMT_REQUEST_HEADER_TITLE = style("\r\n  %s:\r\n", white, bold);
const FMT_REQUEST_HEADER_NAME = style("    %s", bold);
const FMT_REQUEST_HEADER_COLON = style("%s ", gray);
const FMT_REQUEST_HEADER_VALUE = style("%s", gray, italic);
const FMT_RESPONSE_STATUS = style("\n%s", yellow);
const FMT_RESPONSE_TEXT = style("%s");
const FMT_RESPONSE_HEADER_TITLE = style("\r\n  %s:\r\n", white, bold);
const FMT_RESPONSE_HEADER_NAME = style("    %s", bold);
const FMT_RESPONSE_HEADER_COLON = style("%s ", gray);
const FMT_RESPONSE_HEADER_VALUE = style("%s", gray, italic);
const FMT_RESPONSE_CONTENT_TITLE = style("\r\n\r\n  %s:\r\n", white, bold);
const FMT_RESPONSE_CONTENT = (content: string, language: string): string =>
  "\r\n    " +
  highlight(defaultTheme, language, content.trim()).split("\n").join("\n    ")
    .trim();

const languageTable = {
  "/typescript": "tsx",
  "/javascript": "tsx",
  "/json": "json",
  "+json": "json",
  "/toml": "toml",
  "/markdown": "markdown",
  "/rust": "rust",
  "/html": "html",
  "/css": "css",
  "/python": "python",
  "/lua": "lua",
  "/go": "go",
  "/bash": "bash",
  "/jsstacktrace": "jsstacktrace",
  "/yaml": "yaml",
  "/haml": "haml",
  "/ini": "ini",
};

export async function vfetch(
  request: Request,
  {
    log = console.debug,
    fmtMethod = FMT_METHOD,
    fmtUrlProtocol = FMT_URL_PROTOCOL,
    fmtUrlColonSlashSlash = FMT_URL_COLON_SLASH_SLASH,
    fmtUrlHostname = FMT_URL_HOSTNAME,
    fmtUrlPortColon = FMT_URL_PORT_COLON,
    fmtUrlPort = FMT_URL_PORT,
    fmtUrlPathname = FMT_URL_PATHNAME,
    fmtUrlQueryQuestionmark = FMT_URL_QUERY_QUESTIONMARK,
    fmtUrlQueryAmpersand = FMT_URL_QUERY_AMPERSAND,
    fmtUrlQueryKey = FMT_URL_QUERY_KEY,
    fmtUrlQueryEquals = FMT_URL_QUERY_EQUALS,
    fmtUrlQueryValue = FMT_URL_QUERY_VALUE,
    fmtUrlHashHashtag = FMT_URL_HASH_HASHTAG,
    fmtUrlHash = FMT_URL_HASH,
    fmtRequestHeaderTitle = FMT_REQUEST_HEADER_TITLE,
    fmtRequestHeaderName = FMT_REQUEST_HEADER_NAME,
    fmtRequestHeaderColon = FMT_REQUEST_HEADER_COLON,
    fmtRequestHeaderValue = FMT_REQUEST_HEADER_VALUE,
    fmtResponseStatus = FMT_RESPONSE_STATUS,
    fmtResponseText = FMT_RESPONSE_TEXT,
    fmtResponseHeaderTitle = FMT_RESPONSE_HEADER_TITLE,
    fmtResponseHeaderName = FMT_RESPONSE_HEADER_NAME,
    fmtResponseHeaderColon = FMT_RESPONSE_HEADER_COLON,
    fmtResponseHeaderValue = FMT_RESPONSE_HEADER_VALUE,
    fmtResponseContentTitle = FMT_RESPONSE_CONTENT_TITLE,
    fmtContent = FMT_RESPONSE_CONTENT,
  }: VerboseFetchOptions = {},
) {
  const url = new URL(request.url);
  const response = await fetch(request);

  let language: undefined | string = undefined;
  let content: undefined | string = undefined;
  const contentType = response.headers.get("content-type");

  if (contentType) {
    for (const [key, value] of Object.entries(languageTable)) {
      if (contentType.toLowerCase().includes(key)) {
        language = value;
        if (language === "json") {
          content = JSON.stringify(await response.json(), null, 2);
        } else {
          content = await response.clone().text();
        }
        break;
      }
    }
  }

  log(
    ...(
      new MessageFormatter()
        .push(fmtMethod, request.method).push(S)
        .push(fmtUrlProtocol, url.protocol.slice(0, -1))
        .push(fmtUrlColonSlashSlash, "://")
        .push(fmtUrlHostname, url.hostname)
        .pushIf(!!url.port, fmtUrlPortColon, ":")
        .pushIf(!!url.port, fmtUrlPort, url.port.substring(1))
        .push(fmtUrlPathname, url.pathname)
        .array(
          [...url.searchParams.entries()],
          (builder, [key, value], index) =>
            builder
              .pushIf(index === 0, fmtUrlQueryQuestionmark, "?")
              .pushIf(index !== 0, fmtUrlQueryAmpersand, "&")
              .push(fmtUrlQueryKey, key)
              .pushIf(!!value, fmtUrlQueryEquals, "=")
              .pushIf(!!value, fmtUrlQueryValue, value),
        )
        .pushIf(!!url.hash, fmtUrlHashHashtag, "#")
        .pushIf(!!url.hash, fmtUrlHash, url.hash.substring(1))
        .array(
          [...request.headers.entries()],
          (builder, [key, value], index) =>
            builder
              .pushIf(index === 0, "\n")
              .pushIf(index === 0, fmtRequestHeaderTitle, "Request Headers")
              .push("\n")
              .push(fmtRequestHeaderName, key)
              .push(fmtRequestHeaderColon, ":")
              .push(fmtRequestHeaderValue, value),
        )
        .push("\n")
        .push(fmtResponseStatus, response.status)
        .push(S)
        .push(fmtResponseText, response.statusText || "")
        .array(
          [...response.headers.entries()],
          (builder, [key, value], index) =>
            builder
              .pushIf(index === 0, "\n")
              .pushIf(index === 0, fmtResponseHeaderTitle, "Request Headers")
              .push("\n")
              .push(fmtResponseHeaderName, key)
              .push(fmtResponseHeaderColon, ":")
              .push(fmtResponseHeaderValue, value),
        )
        .pushIf(
          typeof language === "string" && !!content,
          fmtResponseContentTitle,
          "Response Content",
        )
        .pushIf(
          typeof language === "string" && !!content,
          S,
        )
        .runIf(
          typeof language === "string" && !!content,
          "%s",
          () => fmtContent(content!, language!),
        )
        .build()
    ),
  );
  return response;
}

export function createRequest(
  ...args: ConstructorParameters<typeof Request>
): Request {
  return new Request(...args);
}
