import { setServerPlatform } from '@qwik.dev/core/server';
import type {
  ClientConn,
  ServerRenderOptions,
  ServerRequestEvent,
} from '@qwik.dev/router/middleware/request-handler';
import {
  isStaticPath,
  mergeHeadersCookies,
  requestHandler,
} from '@qwik.dev/router/middleware/request-handler';
import { MIME_TYPES } from '../request-handler/mime-types';
import { limitRequestBody } from '../request-handler/request-body-limit';
import { normalizeRequestUrl } from '../shared/url';
// @ts-ignore
import { extname, fromFileUrl, join } from 'https://deno.land/std/path/mod.ts';
import { isDev } from '@qwik.dev/core/build';

// @qwik.dev/router/middleware/deno

/** @public */
export interface NetAddr {
  transport: 'tcp' | 'udp';
  hostname: string;
  port: number;
}

/** @public */
export interface ServeHandlerInfo {
  remoteAddr: NetAddr;
}

/** @public */
export interface QwikRouterDenoMiddleware {
  router: (request: Request, info: ServeHandlerInfo) => Promise<Response | null>;
  /** @deprecated `router` handles 404 responses. Will be removed in V3. */
  notFound: (request: Request) => Promise<Response>;
  staticFile: (request: Request) => Promise<Response | null>;
}

function getRequestUrl(request: Request, opts: QwikCityDenoOptions, info?: ServeHandlerInfo) {
  const url = new URL(request.url);
  const origin = opts.getOrigin?.(request, info) ?? Deno.env?.get?.('ORIGIN');
  if (!origin) {
    return url;
  }
  return normalizeRequestUrl(`${url.pathname}${url.search}${url.hash}`, origin);
}

/** @public */
export function createQwikRouter(opts: QwikRouterDenoOptions): QwikRouterDenoMiddleware {
  if (opts.manifest) {
    setServerPlatform(opts.manifest);
  }

  const staticFolder = opts.static?.root ?? join(fromFileUrl(import.meta.url), '..', '..', 'dist');

  async function router(request: Request, info: ServeHandlerInfo) {
    try {
      const url = getRequestUrl(request, opts, info);

      const serverRequestEv: ServerRequestEvent<Response> = {
        mode: 'server',
        locale: undefined,
        url,
        // @ts-ignore
        env: Deno.env,
        request: limitRequestBody(request, opts.requestBodyLimit),
        getWritableStream: (status, headers, cookies, resolve) => {
          const { readable, writable } = new TransformStream<Uint8Array>();
          const response = new Response(readable, {
            status,
            headers: mergeHeadersCookies(headers, cookies),
          });
          resolve(response);
          return writable;
        },
        platform: {
          ssr: true,
        },
        getClientConn: () => {
          return opts.getClientConn
            ? opts.getClientConn(request, info)
            : {
                ip: info.remoteAddr.hostname,
              };
        },
      };

      // send request to qwik router request handler
      const handledResponse = await requestHandler(serverRequestEv, opts);
      if (handledResponse) {
        handledResponse.completion.then((completion) => {
          if (completion) {
            console.error(completion);
          }
        });
        const response = await handledResponse.response;
        if (response) {
          return response;
        }
      }

      // qwik router did not have a route for this request
      return null;
    } catch (e: any) {
      console.error(e);
      return new Response(isDev ? String(e || 'Error') : 'Internal Server Error', {
        status: 500,
        headers: { 'Content-Type': 'text/plain; charset=utf-8', 'X-Error': 'deno-server' },
      });
    }
  }

  /** @deprecated `router` handles 404 responses. Will be removed in V3. */
  const notFound = async (_request: Request) => {
    return null as never;
  };

  const openStaticFile = async (url: URL) => {
    const pathname = url.pathname;
    const fileName = pathname.slice(url.pathname.lastIndexOf('/'));
    let filePath: string;
    if (fileName.includes('.')) {
      filePath = join(staticFolder, pathname);
    } else if (!globalThis.__NO_TRAILING_SLASH__) {
      filePath = join(staticFolder, pathname + 'index.html');
    } else {
      filePath = join(staticFolder, pathname, 'index.html');
    }
    return {
      filePath,
      // @ts-ignore
      content: await Deno.open(filePath, { read: true }),
    };
  };

  const staticFile = async (request: Request) => {
    try {
      const url = getRequestUrl(request, opts);

      if (isStaticPath(request.method || 'GET', url)) {
        const { filePath, content } = await openStaticFile(url);
        const ext = extname(filePath).replace(/^\./, '');

        return new Response(content.readable, {
          status: 200,
          headers: {
            'content-type': MIME_TYPES[ext] || 'text/plain; charset=utf-8',
            'Cache-Control': opts.static?.cacheControl || 'max-age=3600',
          },
        });
      }

      return null;
    } catch (e) {
      console.error(e);
      return new Response(isDev ? String(e || 'Error') : 'Internal Server Error', {
        status: 500,
        headers: { 'Content-Type': 'text/plain; charset=utf-8', 'X-Error': 'deno-server' },
      });
    }
  };

  return {
    router,
    notFound,
    staticFile,
  };
}

/**
 * @deprecated Use `createQwikRouter` instead. Will be removed in V3
 * @public
 */
export const createQwikCity = createQwikRouter;

/** @public */
export interface QwikRouterDenoOptions extends ServerRenderOptions {
  /** Maximum request body size in bytes. Defaults to 10 MiB. */
  requestBodyLimit?: number;

  /** Options for serving static files */
  static?: {
    /** The root folder for statics files. Defaults to /dist */
    root?: string;
    /** Set the Cache-Control header for all static files */
    cacheControl?: string;
  };

  /**
   * Provide a function that computes the origin of the server, used to resolve relative URLs and
   * validate the request origin against CSRF attacks.
   *
   * When not specified, it defaults to the `ORIGIN` environment variable (if set).
   *
   * If `ORIGIN` is not set, it's derived from the incoming request, which is not recommended for
   * production use.
   */
  getOrigin?: (request: Request, info?: ServeHandlerInfo) => string | null;

  /** Provide a function that returns a `ClientConn` for the given request. */
  getClientConn?: (request: Request, info: ServeHandlerInfo) => ClientConn;
}

/**
 * @deprecated Use `QwikRouterDenoOptions` instead. Will be removed in V3
 * @public
 */
export type QwikCityDenoOptions = QwikRouterDenoOptions;

declare const Deno: any;
