import * as plugins from '../plugins.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';

import { Handler } from './classes.handler.js';
import { Compressor, type TCompressionMethod, type ICompressionResult } from './classes.compressor.js';

export class HandlerStatic extends Handler {
  public compressor = new Compressor();
  constructor(
    pathArg: string,
    optionsArg?: {
      requestModifier?: interfaces.TRequestModifier;
      responseModifier?: interfaces.TResponseModifier;
      headers?: { [key: string]: string };
      serveIndexHtmlDefault?: boolean;
      enableCompression?: boolean;
      preferredCompressionMethod?: TCompressionMethod;
    }
  ) {
    super('GET', async (req, res) => {
      let requestPath = req.path;
      let requestHeaders = req.headers;
      let requestBody = req.body;
      let travelData: unknown;
      if (optionsArg && optionsArg.requestModifier) {
        const modifiedRequest = await optionsArg.requestModifier({
          headers: requestHeaders,
          path: requestPath,
          body: requestBody,
        });

        requestHeaders = modifiedRequest.headers;
        requestPath = modifiedRequest.path;
        requestBody = modifiedRequest.body;
        travelData = modifiedRequest.travelData;
      }

      // lets compute some paths
      let filePath: string = requestPath.slice(req.route.path.length - 1); // lets slice of the root
      if (requestPath === '') {
        console.log('replaced root with index.html');
        filePath = 'index.html';
      }
      console.log(filePath);
      const joinedPath = plugins.path.join(pathArg, filePath);
      const defaultPath = plugins.path.join(pathArg, 'index.html');
      let parsedPath = plugins.path.parse(joinedPath);
      let usedPath: string;

      // important security checks
      if (
        requestPath.includes('..') || // don't allow going up the filePath
        requestPath.includes('~') || // don't allow referencing of home directory
        !joinedPath.startsWith(pathArg) // make sure the joined path is within the directory
      ) {
        res.writeHead(500);
        res.end();
        return;
      }

      // set additional headers
      if (optionsArg && optionsArg.headers) {
        for (const key of Object.keys(optionsArg.headers)) {
          res.set(key, optionsArg.headers[key]);
        }
      }

      // lets actually care about serving, if security checks pass
      let fileBuffer: Buffer;
      try {
        fileBuffer = plugins.smartfile.fs.toBufferSync(joinedPath);
        usedPath = joinedPath;
      } catch (err) {
        // try serving index.html instead
        console.log(`could not resolve ${joinedPath}`);
        if (optionsArg && optionsArg.serveIndexHtmlDefault) {
          console.log(`serving default path ${defaultPath} instead of ${joinedPath}`);
          try {
            parsedPath = plugins.path.parse(defaultPath);
            fileBuffer = plugins.smartfile.fs.toBufferSync(defaultPath);
            usedPath = defaultPath;
          } catch (err) {
            res.writeHead(500);
            res.end('File not found!');
            return;
          }
        } else {
          res.writeHead(500);
          res.end('File not found!');
          return;
        }
      }

      res.type(parsedPath.ext);

      const headers = res.getHeaders();

      // lets modify the response at last
      if (optionsArg && optionsArg.responseModifier) {
        const modifiedResponse = await optionsArg.responseModifier({
          headers: res.getHeaders(),
          path: usedPath,
          responseContent: fileBuffer,
          travelData,
        });

        // headers
        for (const key of Object.keys(res.getHeaders())) {
          if (!modifiedResponse.headers[key]) {
            res.removeHeader(key);
          }
        }

        for (const key of Object.keys(modifiedResponse.headers)) {
          res.setHeader(key, modifiedResponse.headers[key]);
        }

        // responseContent
        fileBuffer = modifiedResponse.responseContent;
      }

      // lets finally deal with compression
      let compressionResult: ICompressionResult;

      if (optionsArg && optionsArg.enableCompression) {
        compressionResult = await this.compressor.maybeCompress(requestHeaders, fileBuffer, [optionsArg.preferredCompressionMethod]);
      } else {
        compressionResult = {
          compressionMethod: 'none',
          result: fileBuffer,
        };
      }

      res.status(200);
      if (compressionResult?.compressionMethod) {
        res.header('Content-Encoding', compressionResult.compressionMethod);
        res.write(compressionResult.result);
      } else {
        res.write(fileBuffer);
      }
      res.end();
    });
  }
}