import type { ServerResponse } from "node:http";

export function parseHttpUrl(value: string, errorMessage: string): URL {
  const url = new URL(value.trim());

  if (url.protocol !== "https:" && url.protocol !== "http:") {
    throw new Error(errorMessage);
  }

  if (url.username || url.password) {
    throw new Error("URL credentials are not allowed.");
  }

  return url;
}

export function resolveDavUrlFromShareUrl(shareUrl: string): string {
  const url = parseHttpUrl(shareUrl, "Please enter a public Nextcloud share link.");
  const publicShare = url.pathname.match(/\/s\/([^/?#]+)/);

  if (publicShare) {
    return `${url.origin}/public.php/dav/files/${publicShare[1]}/`;
  }

  const davShare = url.pathname.match(/\/public\.php\/dav\/files\/([^/?#]+)\/?/);

  if (davShare) {
    return `${url.origin}/public.php/dav/files/${davShare[1]}/`;
  }

  throw new Error("Please enter a public Nextcloud share link.");
}

export function resolveValidatedBlobUrl(targetUrl: string, shareUrl: string): string {
  const target = parseHttpUrl(targetUrl, "Invalid image URL.");
  const shareDavUrl = new URL(resolveDavUrlFromShareUrl(shareUrl));

  if (target.origin !== shareDavUrl.origin || !target.pathname.startsWith(shareDavUrl.pathname)) {
    throw new Error("Image URL is outside the requested Nextcloud share.");
  }

  return target.toString();
}

async function proxyUpstream(url: string, init?: RequestInit) {
  const upstream = await fetch(url, init);
  const body = await upstream.arrayBuffer();

  return {
    body: Buffer.from(body),
    contentType: upstream.headers.get("content-type") ?? "application/octet-stream",
    status: upstream.status
  };
}

export async function handleNextcloudListRequest(url: URL, res: ServerResponse): Promise<void> {
  const share = url.searchParams.get("share");

  if (!share) {
    res.statusCode = 400;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.end(JSON.stringify({ error: "missing `share` parameter" }));
    return;
  }

  try {
    const davUrl = resolveDavUrlFromShareUrl(share);
    const xml =
      '<?xml version="1.0" encoding="UTF-8"?>' +
      '<d:propfind xmlns:d="DAV:">' +
        "<d:prop>" +
          "<d:displayname/>" +
          "<d:getlastmodified/>" +
          "<d:getcontenttype/>" +
          "<d:getcontentlength/>" +
          "<d:resourcetype/>" +
        "</d:prop>" +
      "</d:propfind>";

    const upstream = await proxyUpstream(davUrl, {
      method: "PROPFIND",
      headers: {
        Depth: "1",
        "Content-Type": "application/xml; charset=utf-8",
        Accept: "application/xml, text/xml"
      },
      body: xml
    });

    res.statusCode = upstream.status;
    res.setHeader("content-type", upstream.contentType);
    res.end(upstream.body);
  } catch (error) {
    res.statusCode = 502;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.end(
      JSON.stringify({
        error: error instanceof Error ? error.message : "upstream request failed"
      })
    );
  }
}

export async function handleNextcloudBlobRequest(url: URL, res: ServerResponse): Promise<void> {
  const target = url.searchParams.get("url");
  const share = url.searchParams.get("share");

  if (!target || !share) {
    res.statusCode = 400;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.end(JSON.stringify({ error: "missing `share` or `url` parameter" }));
    return;
  }

  let validatedTarget: string;

  try {
    validatedTarget = resolveValidatedBlobUrl(target, share);
  } catch (error) {
    res.statusCode = 400;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.end(
      JSON.stringify({
        error: error instanceof Error ? error.message : "invalid image URL"
      })
    );
    return;
  }

  try {
    const upstream = await proxyUpstream(validatedTarget);
    res.statusCode = upstream.status;
    res.setHeader("content-type", upstream.contentType);
    res.end(upstream.body);
  } catch (error) {
    res.statusCode = 502;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.end(
      JSON.stringify({
        error: error instanceof Error ? error.message : "upstream request failed"
      })
    );
  }
}
