import { EventEmitter } from "stream";
import { UserInterface } from "../../../models/user-model";
import { Response } from "express";
import ForbiddenError from "../../../utils/ForbiddenError";
import NotFoundError from "../../../utils/NotFoundError";
import crypto from "crypto";
import { createGenericParams } from "./storageHelper";
import { getStorageActions } from "../actions/helper-actions";
import { getFileDB, getUserDB } from "../../../db/dbFactory";
import NotAuthorizedError from "../../../utils/NotAuthorizedError";
import sanitizeFilename from "../../../utils/sanitizeFilename";

const fileDB = getFileDB();
const userDB = getUserDB();

const storageActions = getStorageActions();

const proccessData = (res: Response, fileID: string, tempToken: string) => {
  const eventEmitter = new EventEmitter();

  const processFile = async () => {
    try {
      const file = await fileDB.getPublicFile(fileID);

      if (!file || !file.metadata.link || file.metadata.link !== tempToken) {
        throw new NotAuthorizedError("File Not Public");
      }

      if (file.metadata.linkType === "one") {
        await fileDB.removeOneTimePublicLink(fileID);
      }

      const user = await userDB.getUserInfo(file.metadata.owner);

      if (!user) throw new NotFoundError("User Not Found");

      const password = user.getEncryptionKey();

      if (!password) throw new ForbiddenError("Invalid Encryption Key");

      const IV = file.metadata.IV;

      const readStreamParams = createGenericParams({
        filePath: file.metadata.filePath,
        Key: file.metadata.s3ID,
      });

      const readStream = storageActions.createReadStream(readStreamParams);

      const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();

      const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);

      decipher.on("error", (e: Error) => {
        eventEmitter.emit("error", e);
      });

      readStream.on("error", (e: Error) => {
        eventEmitter.emit("error", e);
      });

      res.on("error", (e: Error) => {
        eventEmitter.emit("error", e);
      });

      const sanatizedFilename = sanitizeFilename(file.filename);
      const encodedFilename = encodeURIComponent(sanatizedFilename);
      res.set("Content-Type", "binary/octet-stream");
      res.set(
        "Content-Disposition",
        `attachment; filename="${sanatizedFilename}"; filename*=UTF-8''${encodedFilename}`
      );
      res.set("Content-Length", file.metadata.size.toString());

      readStream
        .pipe(decipher)
        .pipe(res)
        .on("finish", () => {
          eventEmitter.emit("finish");
        });
    } catch (e) {
      eventEmitter.emit("error", e);
    }
  };

  processFile();

  return eventEmitter;
};

const getPublicFileData = (
  res: Response,
  fileID: string,
  tempToken: string
) => {
  return new Promise((resolve, reject) => {
    const eventEmitter = proccessData(res, fileID, tempToken);
    eventEmitter.on("finish", (data) => {
      resolve(data);
    });
    eventEmitter.on("error", (e) => {
      reject(e);
    });
  });
};

export default getPublicFileData;
