import db from "../connections/sqlite";
import { ObjectId } from "mongodb";
import { IFileDB } from "../dbTypes";
import { FileListQueryType } from "../../types/file-types";

const rowToFile = (row: any) => {
  if (!row) return null;

  return {
    _id: row.id,
    length: row.length,
    chunkSize: row.chunkSize,
    uploadDate: row.uploadDate,
    filename: row.filename,
    metadata: {
      owner: row.owner,
      parent: row.parent,
      parentList: row.parentList,
      hasThumbnail: !!row.hasThumbnail,
      isVideo: !!row.isVideo,
      thumbnailID: row.thumbnailID || undefined,
      size: row.size,
      IV: row.iv,
      linkType: row.linkType || undefined,
      link: row.link || undefined,
      filePath: row.filePath || undefined,
      s3ID: row.s3ID || undefined,
      personalFile: row.personalFile ? true : undefined,
      trashed: row.trashed ? true : null,
      processingFile: row.processingFile ? true : null,
    },
  };
};

const sortColumn = (sortBy: string): { column: string; direction: string } => {
  if (sortBy === "date_desc") return { column: "uploadDate", direction: "DESC" };
  if (sortBy === "date_asc") return { column: "uploadDate", direction: "ASC" };
  if (sortBy === "alp_desc")
    return { column: "filename COLLATE NOCASE", direction: "DESC" };
  return { column: "filename COLLATE NOCASE", direction: "ASC" };
};

class FileDB implements IFileDB {
  constructor() {}

  // READ

  getPublicFile = async (fileID: string) => {
    const row = db.prepare("SELECT * FROM files WHERE id = ?").get(fileID);
    return rowToFile(row);
  };

  getPublicInfo = async (fileID: string, tempToken: string) => {
    const row = db
      .prepare("SELECT * FROM files WHERE id = ? AND link = ?")
      .get(fileID, tempToken);
    return rowToFile(row);
  };

  getFileInfo = async (fileID: string, userID: string) => {
    const row = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);
    return rowToFile(row);
  };

  getQuickList = async (userID: string, limit: number) => {
    const rows = db
      .prepare(
        `SELECT * FROM files WHERE owner = ? AND trashed = 0 AND processingFile = 0
         ORDER BY uploadDate DESC LIMIT ?`
      )
      .all(userID, limit);
    return rows.map(rowToFile);
  };

  getList = async (
    queryData: FileListQueryType,
    sortBy: string,
    limit: number
  ) => {
    const {
      userID,
      search,
      parent,
      startAtDate,
      startAtName,
      trashMode,
      mediaMode,
      mediaFilter,
    } = queryData;

    const conditions: string[] = ["owner = ?"];
    const params: any[] = [userID];

    if (search && search !== "") {
      conditions.push("filename LIKE ? COLLATE NOCASE");
      params.push(`%${search}%`);
    } else if (!mediaMode) {
      conditions.push("parent = ?");
      params.push(parent);
    }

    if (sortBy === "date_desc" && startAtDate) {
      conditions.push("uploadDate < ?");
      params.push(new Date(startAtDate).toISOString());
    } else if (sortBy === "date_asc" && startAtDate) {
      conditions.push("uploadDate > ?");
      params.push(new Date(startAtDate).toISOString());
    } else if (sortBy === "alp_desc" && startAtName) {
      conditions.push("filename < ? COLLATE NOCASE");
      params.push(startAtName);
    } else if (sortBy === "alp_asc" && startAtName) {
      conditions.push("filename > ? COLLATE NOCASE");
      params.push(startAtName);
    }

    conditions.push("trashed = ?");
    params.push(trashMode ? 1 : 0);

    if (mediaMode) {
      conditions.push("hasThumbnail = 1");
      if (mediaFilter === "photos") conditions.push("isVideo = 0");
      else if (mediaFilter === "videos") conditions.push("isVideo = 1");
    }

    conditions.push("processingFile = 0");

    const { column, direction } = sortColumn(sortBy);

    const rows = db
      .prepare(
        `SELECT * FROM files WHERE ${conditions.join(
          " AND "
        )} ORDER BY ${column} ${direction} LIMIT ?`
      )
      .all(...params, limit);

    return rows.map(rowToFile);
  };

  getFileSearchList = async (
    userID: string,
    searchQuery: RegExp | string,
    trashMode: boolean,
    mediaMode: boolean
  ) => {
    const search =
      searchQuery instanceof RegExp ? searchQuery.source : searchQuery;

    const conditions = [
      "owner = ?",
      "filename LIKE ? COLLATE NOCASE",
      "trashed = ?",
      "processingFile = 0",
    ];
    const params: any[] = [userID, `%${search}%`, trashMode ? 1 : 0];

    if (mediaMode) conditions.push("hasThumbnail = 1");

    const rows = db
      .prepare(
        `SELECT * FROM files WHERE ${conditions.join(" AND ")} LIMIT 10`
      )
      .all(...params);

    return rows.map(rowToFile);
  };

  getFileListByIncludedParent = async (
    userID: string,
    parentListString: string
  ) => {
    const rows = db
      .prepare(
        "SELECT * FROM files WHERE owner = ? AND parentList LIKE ?"
      )
      .all(userID, `%${parentListString}%`);
    return rows.map(rowToFile);
  };

  getFileListByOwner = async (userID: string) => {
    const rows = db.prepare("SELECT * FROM files WHERE owner = ?").all(userID);
    return rows.map(rowToFile);
  };

  getFileListByParent = async (userID: string, parent: string) => {
    const rows = db
      .prepare("SELECT * FROM files WHERE owner = ? AND parent = ?")
      .all(userID, parent);
    return rows.map(rowToFile);
  };

  // UPDATE

  updateFileUploadedFile = async (
    fileID: string,
    userID: string,
    parent: string,
    parentList: string
  ) => {
    db.prepare(
      `UPDATE files SET parent = ?, parentList = ?, processingFile = 0
       WHERE id = ? AND owner = ?`
    ).run(parent, parentList, fileID, userID);

    return this.getFileInfo(fileID, userID);
  };

  updateFolderUploadedFile = async (
    fileID: string,
    userID: string,
    parent: string,
    parentList: string
  ) => {
    return this.updateFileUploadedFile(fileID, userID, parent, parentList);
  };

  setThumbnail = async (fileID: string, thumbnailID: string) => {
    const result = db
      .prepare(
        `UPDATE files SET hasThumbnail = 1, thumbnailID = ?
         WHERE id = ? AND hasThumbnail = 0`
      )
      .run(thumbnailID, fileID);

    if (result.changes === 0) return null;

    const row = db.prepare("SELECT * FROM files WHERE id = ?").get(fileID);
    return rowToFile(row);
  };

  setVideoThumbnail = async (
    fileID: string,
    userID: string,
    thumbnailID: string
  ) => {
    const result = db
      .prepare(
        `UPDATE files SET hasThumbnail = 1, thumbnailID = ?, isVideo = 1
         WHERE id = ? AND owner = ?`
      )
      .run(thumbnailID, fileID, userID);

    if (result.changes === 0) return null;

    return this.getFileInfo(fileID, userID);
  };

  removeOneTimePublicLink = async (fileID: string) => {
    db.prepare(
      "UPDATE files SET linkType = NULL, link = NULL WHERE id = ?"
    ).run(fileID);
    const row = db.prepare("SELECT * FROM files WHERE id = ?").get(fileID);
    return rowToFile(row);
  };

  removeLink = async (fileID: string, userID: string) => {
    db.prepare(
      "UPDATE files SET linkType = NULL, link = NULL WHERE id = ? AND owner = ?"
    ).run(fileID, userID);
    return this.getFileInfo(fileID, userID);
  };

  makePublic = async (fileID: string, userID: string, token: string) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare(
      "UPDATE files SET linkType = 'public', link = ? WHERE id = ? AND owner = ?"
    ).run(token, fileID, userID);

    return this.getFileInfo(fileID, userID);
  };

  makeOneTimePublic = async (fileID: string, userID: string, token: string) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare(
      "UPDATE files SET linkType = 'one', link = ? WHERE id = ? AND owner = ?"
    ).run(token, fileID, userID);

    return this.getFileInfo(fileID, userID);
  };

  trashFile = async (
    fileID: string,
    parent: string,
    parentList: string,
    userID: string
  ) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare(
      "UPDATE files SET trashed = 1, parent = ?, parentList = ? WHERE id = ? AND owner = ?"
    ).run(parent, parentList, fileID, userID);

    return this.getFileInfo(fileID, userID);
  };

  restoreFile = async (fileID: string, userID: string) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare("UPDATE files SET trashed = 0 WHERE id = ? AND owner = ?").run(
      fileID,
      userID
    );

    return this.getFileInfo(fileID, userID);
  };

  removeTempToken = async (user: any, tempToken: string) => {
    user.tempTokens = user.tempTokens.filter(
      (filterToken: any) => filterToken.token !== tempToken
    );
    return user;
  };

  trashFilesByParent = async (parentList: string, userID: string) => {
    const result = db
      .prepare(
        "UPDATE files SET trashed = 1 WHERE owner = ? AND parentList LIKE ?"
      )
      .run(userID, `%${parentList}%`);
    return result;
  };

  restoreFilesByParent = async (parentList: string, userID: string) => {
    const result = db
      .prepare(
        "UPDATE files SET trashed = 0 WHERE owner = ? AND parentList LIKE ?"
      )
      .run(userID, `%${parentList}%`);
    return result;
  };

  renameFile = async (fileID: string, userID: string, title: string) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare("UPDATE files SET filename = ? WHERE id = ? AND owner = ?").run(
      title,
      fileID,
      userID
    );

    return this.getFileInfo(fileID, userID);
  };

  moveFile = async (
    fileID: string,
    userID: string,
    parent: string,
    parentList: string
  ) => {
    const existing: any = db
      .prepare("SELECT * FROM files WHERE id = ? AND owner = ?")
      .get(fileID, userID);

    if (!existing) return null;

    db.prepare(
      "UPDATE files SET parent = ?, parentList = ? WHERE id = ? AND owner = ?"
    ).run(parent, parentList, fileID, userID);

    return this.getFileInfo(fileID, userID);
  };

  moveMultipleFiles = async (
    userID: string,
    currentParent: string,
    newParent: string,
    newParentList: string
  ) => {
    db.prepare(
      "UPDATE files SET parent = ?, parentList = ? WHERE owner = ? AND parent = ?"
    ).run(newParent, newParentList, userID, currentParent);
  };

  // DELETE

  deleteFile = async (fileID: string, userID: string) => {
    const result = db
      .prepare("DELETE FROM files WHERE id = ? AND owner = ?")
      .run(fileID, userID);
    return result;
  };

  // CREATE

  createFile = async (data: {
    filename: string;
    uploadDate: string;
    length: number;
    metadata: any;
  }) => {
    const id = new ObjectId().toString();
    const m = data.metadata || {};

    db.prepare(
      `INSERT INTO files (
        id, filename, length, chunkSize, uploadDate, owner, parent, parentList,
        hasThumbnail, isVideo, thumbnailID, size, iv, linkType, link, filePath,
        s3ID, personalFile, trashed, processingFile
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
    ).run(
      id,
      data.filename,
      data.length,
      null,
      data.uploadDate,
      m.owner,
      m.parent,
      m.parentList,
      m.hasThumbnail ? 1 : 0,
      m.isVideo ? 1 : 0,
      m.thumbnailID || null,
      m.size,
      m.IV,
      m.linkType || null,
      m.link || null,
      m.filePath || null,
      m.s3ID || null,
      m.personalFile ? 1 : 0,
      m.trashed ? 1 : 0,
      m.processingFile ? 1 : 0
    );

    const row = db.prepare("SELECT * FROM files WHERE id = ?").get(id);
    return rowToFile(row);
  };
}

export default FileDB;
