import { NextFunction, Request, Response } from "express";
import FileService from "../services/file-service/file-service";
import { UserInterface } from "../models/user-model";
import {
  createStreamVideoCookie,
  removeStreamVideoCookie,
} from "../cookies/create-cookies";
import ChunkService from "../services/chunk-service/chunk-service";
import streamToBuffer from "../utils/streamToBuffer";
import NotAuthorizedError from "../utils/NotAuthorizedError";
import { FileListQueryType } from "../types/file-types";
import fs from "fs";
import { getUserDB } from "../db/dbFactory";

const fileService = new FileService();
const userDB = getUserDB();
type userAccessType = {
  _id: string;
  emailVerified: boolean;
  email: string;
  s3Enabled: boolean;
};

export interface RequestTypeFullUser extends Request {
  user?: UserInterface;
  encryptedToken?: string;
  accessTokenStreamVideo?: string;
}

interface RequestType extends Request {
  user?: userAccessType;
  encryptedToken?: string;
}

class FileController {
  chunkService;

  constructor() {
    this.chunkService = new ChunkService();
  }

  getThumbnail = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }
    try {
      const user = req.user;
      const id = req.params.id;

      await this.chunkService.getThumbnail(user, id, res);
    } catch (e: unknown) {
      next(e);
    }
  };

  getFullThumbnail = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }
    try {
      const user = req.user;
      const fileID = req.params.id;

      await this.chunkService.getFullThumbnail(user, fileID, res);
    } catch (e: unknown) {
      next(e);
    }
  };

  uploadFile = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;
      const busboy = req.busboy;

      const file = await this.chunkService.uploadFile(user, busboy, req);

      res.send(file);
    } catch (e: unknown) {
      next(e);
    }
  };

  getPublicDownload = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    try {
      const ID = req.params.id;
      const tempToken = req.params.tempToken;

      await this.chunkService.getPublicDownload(ID, tempToken, res);
    } catch (e: unknown) {
      next(e);
    }
  };

  removeLink = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const id = req.params.id;
      const userID = req.user._id;

      const file = await fileService.removeLink(userID, id);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  makePublic = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const fileID = req.params.id;
      const userID = req.user._id;

      const { file, token } = await fileService.makePublic(userID, fileID);

      res.send({ file, token });
    } catch (e) {
      next(e);
    }
  };

  getPublicInfo = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    try {
      const id = req.params.id;
      const tempToken = req.params.tempToken;

      const file = await fileService.getPublicInfo(id, tempToken);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  makeOneTimePublic = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const id = req.params.id;
      const userID = req.user._id;

      const { file, token } = await fileService.makeOneTimePublic(userID, id);

      res.send({ file, token });
    } catch (e) {
      next(e);
    }
  };

  getFileInfo = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const fileID = req.params.id;
      const userID = req.user._id;

      const file = await fileService.getFileInfo(userID, fileID);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  getQuickList = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;
      const limit = Number.parseInt(req.query.limit as string) || 20;

      const quickList = await fileService.getQuickList(user, limit);

      res.send(quickList);
    } catch (e) {
      next(e);
    }
  };

  getList = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const query = req.query;

      const search = (query.search as string) || undefined;
      const parent = (query.parent as string) || "/";
      const limit = Number.parseInt(query.limit as string) || 50;
      const sortBy = (query.sortBy as string) || "date_desc";
      const startAtDate = (query.startAtDate as string) || undefined;
      const startAtName = (query.startAtName as string) || undefined;
      const trashMode = query.trashMode === "true";
      const mediaMode = query.mediaMode === "true";
      const mediaFilter = (query.mediaFilter as string) || "all";

      const queryData: FileListQueryType = {
        userID,
        search,
        parent,
        startAtDate,
        startAtName,
        trashMode,
        mediaMode,
        sortBy,
        mediaFilter,
      };

      const fileList = await fileService.getList(queryData, sortBy, limit);

      res.send(fileList);
    } catch (e) {
      next(e);
    }
  };

  getDownloadToken = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;

      const tempToken = await fileService.getDownloadToken(user);

      res.send({ tempToken });
    } catch (e) {
      next(e);
    }
  };

  getAccessTokenStreamVideo = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) return;

    try {
      const user = req.user;

      const currentUUID = req.headers.uuid as string;

      const streamVideoAccessToken = await user.generateAuthTokenStreamVideo(
        currentUUID
      );

      createStreamVideoCookie(res, streamVideoAccessToken);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  removeStreamVideoAccessToken = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) return;

    try {
      const userID = req.user._id;

      const accessTokenStreamVideo = req.accessTokenStreamVideo!;

      if (!accessTokenStreamVideo) {
        throw new NotAuthorizedError("No Access Token");
      }

      await userDB.removeTempTokenByValue(
        userID.toString(),
        accessTokenStreamVideo
      );

      removeStreamVideoCookie(res);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  removeTempToken = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;
      const tempToken = req.params.tempToken;
      const currentUUID = req.params.uuid;

      await fileService.removeTempToken(user, tempToken, currentUUID);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  streamVideo = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;
      const fileID = req.params.id;
      const headers = req.headers;

      await this.chunkService.streamVideo(user, fileID, headers, res);
    } catch (e: unknown) {
      next(e);
    }
  };

  streamVideoTest = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const headers = req.headers;
      console.log("headers", headers.range);
      const fileSize = 26867866;
      const range = headers.range!;
      const parts = range.replace(/bytes=/, "").split("-");
      const start = parseInt(parts[0], 10);
      const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
      const chunksize = end - start + 1;

      const readStream = fs.createReadStream(
        "/Users/kylehoell/Developer/myDrive-4/upgrade/old/video.mp4",
        { start, end }
      );

      const head = {
        "Content-Range": "bytes " + start + "-" + end + "/" + fileSize,
        "Accept-Ranges": "bytes",
        "Content-Length": chunksize,
        "Content-Type": "video/mp4",
      };

      res.writeHead(206, head);

      readStream.on("data", (data) => {
        res.write(data);
      });

      readStream.on("end", () => {
        res.end();
      });
    } catch (e: unknown) {
      next(e);
    }
  };

  downloadFile = async (
    req: RequestTypeFullUser,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const user = req.user;
      const fileID = req.params.id;

      await this.chunkService.downloadFile(user, fileID, res);
    } catch (e: unknown) {
      next(e);
    }
  };

  getSuggestedList = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const searchQuery = req.query.search as string;
      const trashMode = req.query.trashMode === "true";
      const mediaMode = req.query.mediaMode === "true";

      const { fileList, folderList } = await fileService.getSuggestedList(
        userID,
        searchQuery,
        trashMode,
        mediaMode
      );

      return res.send({ folderList, fileList });
    } catch (e) {
      next(e);
    }
  };

  renameFile = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const fileID = req.body.id;
      const title = req.body.title;
      const userID = req.user._id;

      const file = await fileService.renameFile(userID, fileID, title);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  moveFile = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const fileID = req.body.id as string;
      const userID = req.user._id as string;
      const parentID = (req.body.parentID as string) || "/";

      const file = await fileService.moveFile(userID, fileID, parentID);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  moveMultiFile = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const items = req.body.items;
      const parentID = (req.body.parentID as string) || "/";

      await fileService.moveMultiFiles(userID, items, parentID);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  trashFile = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const fileID = req.body.id;

      const trashedFile = await fileService.trashFile(userID, fileID);

      res.send(trashedFile);
    } catch (e) {
      next(e);
    }
  };

  restoreFile = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const fileID = req.body.id;

      const file = await fileService.restoreFile(userID, fileID);

      res.send(file);
    } catch (e) {
      next(e);
    }
  };

  deleteFile = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const fileID = req.body.id;

      await this.chunkService.deleteFile(userID, fileID);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  deleteMulti = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const items = req.body.items;

      await this.chunkService.deleteMulti(userID, items);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  trashMulti = async (req: RequestType, res: Response, next: NextFunction) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const items = req.body.items;

      await fileService.trashMulti(userID, items);

      res.send();
    } catch (e) {
      next(e);
    }
  };

  restoreMulti = async (
    req: RequestType,
    res: Response,
    next: NextFunction
  ) => {
    if (!req.user) {
      return;
    }

    try {
      const userID = req.user._id;
      const items = req.body.items;

      await fileService.restoreMulti(userID, items);

      res.send();
    } catch (e) {
      next(e);
    }
  };
}

export default FileController;
