import db from "../connections/sqlite";
import { ObjectId } from "mongodb";
import { IFolderDB } from "../dbTypes";
import { FolderListQueryType } from "../../types/folder-types";

const encodeParentList = (parentList: (string | any)[]): string =>
  parentList.map((id) => id.toString()).join(",");

const decodeParentList = (parentList: string): string[] =>
  parentList ? parentList.split(",").filter((p) => p !== "") : [];

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

  return {
    _id: row.id,
    name: row.name,
    parent: row.parent,
    owner: row.owner,
    parentList: decodeParentList(row.parentList),
    personalFolder: row.personalFolder ? true : undefined,
    trashed: row.trashed ? true : null,
    createdAt: new Date(row.createdAt),
    updatedAt: new Date(row.updatedAt),
  };
};

// Wraps the stored comma-joined parentList with delimiters at query time so
// membership checks can't match a partial id (mirrors Mongo's array $in/$all/$nin
// semantics, which match whole elements, not substrings).
const containsClause = () => "(',' || parentList || ',') LIKE ?";
const containsParam = (id: string) => `%,${id},%`;

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

class FolderDB implements IFolderDB {
  constructor() {}

  // READ

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

    const rows = db
      .prepare(
        `SELECT * FROM folders WHERE owner = ? AND name LIKE ? COLLATE NOCASE AND trashed = ?
         LIMIT 10`
      )
      .all(userID, `%${search}%`, trashMode ? 1 : 0);

    return rows.map(rowToFolder);
  };

  getFolderInfo = async (folderID: string, userID: string) => {
    const row = db
      .prepare("SELECT * FROM folders WHERE id = ? AND owner = ?")
      .get(folderID, userID);
    return rowToFolder(row);
  };

  getFolderList = async (queryData: FolderListQueryType, sortBy: string) => {
    const { userID, search, parent, trashMode } = queryData;

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

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

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

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

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

    return rows.map(rowToFolder);
  };

  getMoveFolderList = async (
    userID: string,
    parent = "/",
    search?: string,
    folderIDs?: string[]
  ) => {
    const conditions = ["owner = ?", "trashed = 0"];
    const params: any[] = [userID];

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

    const rows = db
      .prepare(
        `SELECT * FROM folders WHERE ${conditions.join(
          " AND "
        )} ORDER BY createdAt DESC`
      )
      .all(...params) as any[];

    const filtered =
      folderIDs && folderIDs.length > 0
        ? rows.filter((row) => {
            if (folderIDs.includes(row.id)) return false;
            const list = decodeParentList(row.parentList);
            return !folderIDs.some((id) => list.includes(id));
          })
        : rows;

    return filtered.map(rowToFolder);
  };

  getFolderListByIncludedParent = async (userID: string, parent: string) => {
    const rows = db
      .prepare(
        `SELECT * FROM folders WHERE owner = ? AND ${containsClause()} AND trashed = 0`
      )
      .all(userID, containsParam(parent));
    return rows.map(rowToFolder);
  };

  findAllFoldersByParent = async (parentID: string, userID: string) => {
    const rows = db
      .prepare(`SELECT * FROM folders WHERE owner = ? AND ${containsClause()}`)
      .all(userID, containsParam(parentID));
    return rows.map(rowToFolder);
  };

  // UPDATE

  moveFolder = async (
    folderID: string,
    userID: string,
    parent: string,
    parentList: string[]
  ) => {
    const existing: any = db
      .prepare("SELECT * FROM folders WHERE id = ? AND owner = ?")
      .get(folderID, userID);

    if (!existing) return null;

    db.prepare(
      "UPDATE folders SET parent = ?, parentList = ?, updatedAt = ? WHERE id = ? AND owner = ?"
    ).run(
      parent,
      encodeParentList(parentList),
      new Date().toISOString(),
      folderID,
      userID
    );

    return this.getFolderInfo(folderID, userID);
  };

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

    if (!existing) return null;

    db.prepare(
      "UPDATE folders SET name = ?, updatedAt = ? WHERE id = ? AND owner = ?"
    ).run(title, new Date().toISOString(), folderID, userID);

    return this.getFolderInfo(folderID, userID);
  };

  trashFoldersByParent = async (parentList: string[], userID: string) => {
    const clauses = parentList.map(() => containsClause()).join(" AND ");
    const params = parentList.map((id) => containsParam(id));

    const result = db
      .prepare(
        `UPDATE folders SET trashed = 1, updatedAt = ? WHERE owner = ? AND ${clauses}`
      )
      .run(new Date().toISOString(), userID, ...params);
    return result;
  };

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

    if (!existing) return null;

    db.prepare(
      "UPDATE folders SET trashed = 0, updatedAt = ? WHERE id = ? AND owner = ?"
    ).run(new Date().toISOString(), folderID, userID);

    return this.getFolderInfo(folderID, userID);
  };

  restoreFoldersByParent = async (parentList: string[], userID: string) => {
    const clauses = parentList.map(() => containsClause()).join(" AND ");
    const params = parentList.map((id) => containsParam(id));

    const result = db
      .prepare(
        `UPDATE folders SET trashed = 0, updatedAt = ? WHERE owner = ? AND ${clauses}`
      )
      .run(new Date().toISOString(), userID, ...params);
    return result;
  };

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

    if (!existing) return null;

    db.prepare(
      "UPDATE folders SET trashed = 1, updatedAt = ? WHERE id = ? AND owner = ?"
    ).run(new Date().toISOString(), folderID, userID);

    return this.getFolderInfo(folderID, userID);
  };

  // CREATE

  createFolder = async (folderData: {
    name: string;
    parent: string;
    parentList: string[];
    owner: string;
  }) => {
    const id = new ObjectId().toString();
    const now = new Date().toISOString();

    db.prepare(
      `INSERT INTO folders (id, name, parent, owner, parentList, personalFolder, trashed, createdAt, updatedAt)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
    ).run(
      id,
      folderData.name,
      folderData.parent,
      folderData.owner,
      encodeParentList(folderData.parentList),
      0,
      0,
      now,
      now
    );

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

  // DELETE

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

  deleteFoldersByParentList = async (
    parentList: string[],
    userID: string
  ) => {
    const clauses = parentList.map(() => containsClause()).join(" AND ");
    const params = parentList.map((id) => containsParam(id.toString()));

    const result = db
      .prepare(`DELETE FROM folders WHERE owner = ? AND ${clauses}`)
      .run(userID, ...params);
    return result;
  };

  deleteFoldersByOwner = async (userID: string) => {
    const result = db
      .prepare("DELETE FROM folders WHERE owner = ?")
      .run(userID);
    return result;
  };
}

export default FolderDB;
