import { PERMISSIONS } from "@/constants/userPermissions";
import { Type, User } from "@/interfaces/user";
import { createColumnHelper } from "@tanstack/react-table";
import axios from "axios";
import clsx from "clsx";
import { capitalize } from "lodash";
import moment from "moment";
import { useEffect, useMemo, useState } from "react";
import {
  BsCheck,
  BsCheckCircle,
  BsFillExclamationOctagonFill,
  BsTrash,
} from "react-icons/bs";
import { toast } from "react-toastify";
import { countries, TCountries } from "countries-list";
import countryCodes from "country-codes-list";
import Modal from "@/components/Modal";
import UserCard from "@/components/UserCard";
import { USER_TYPE_LABELS } from "@/resources/user";
import useFilterStore from "@/stores/listFilterStore";
import { useRouter } from "next/router";
import { mapBy } from "@/utils";
import { exportListToExcel } from "@/utils/users";
import usePermissions from "@/hooks/usePermissions";
import useUserBalance from "@/hooks/useUserBalance";
import useEntitiesUsers from "@/hooks/useEntitiesUsers";
import { WithLabeledEntities } from "@/interfaces/entity";
import Table from "@/components/High/Table";
import useEntities from "@/hooks/useEntities";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";

const columnHelper = createColumnHelper<WithLabeledEntities<User>>();
const searchFields = [["name"], ["email"], ["entities", ""]];

export default function UserList({
  user,
  filters = [],
  type,
  renderHeader,
}: {
  user: User;
  filters?: ((user: User) => boolean)[];
  type?: Type;
  renderHeader?: (total: number) => JSX.Element;
}) {
  const [showDemographicInformation, setShowDemographicInformation] =
    useState(false);
  const [selectedUser, setSelectedUser] = useState<User>();

  const { users, isLoading, reload } = useEntitiesUsers(type);
  const { entities } = useEntities();

  const isAdmin = useMemo(
    () => ["admin", "developer"].includes(user?.type),
    [user?.type]
  );

  const entitiesViewStudents = useAllowedEntities(
    user,
    entities,
    "view_students"
  );
  const entitiesEditStudents = useAllowedEntities(
    user,
    entities,
    "edit_students"
  );
  const entitiesDeleteStudents = useAllowedEntities(
    user,
    entities,
    "delete_students"
  );

  const entitiesViewTeachers = useAllowedEntities(
    user,
    entities,
    "view_teachers"
  );
  const entitiesEditTeachers = useAllowedEntities(
    user,
    entities,
    "edit_teachers"
  );
  const entitiesDeleteTeachers = useAllowedEntities(
    user,
    entities,
    "delete_teachers"
  );

  const entitiesViewCorporates = useAllowedEntities(
    user,
    entities,
    "view_corporates"
  );
  const entitiesEditCorporates = useAllowedEntities(
    user,
    entities,
    "edit_corporates"
  );
  const entitiesDeleteCorporates = useAllowedEntities(
    user,
    entities,
    "delete_corporates"
  );

  const entitiesViewMasterCorporates = useAllowedEntities(
    user,
    entities,
    "view_mastercorporates"
  );
  const entitiesEditMasterCorporates = useAllowedEntities(
    user,
    entities,
    "edit_mastercorporates"
  );
  const entitiesDeleteMasterCorporates = useAllowedEntities(
    user,
    entities,
    "delete_mastercorporates"
  );

  const entitiesDownloadUsers = useAllowedEntities(
    user,
    entities,
    "download_user_list"
  );

  const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
  const router = useRouter();

  const expirationDateColor = (date: Date) => {
    const momentDate = moment(date);
    const today = moment(new Date());

    if (today.isAfter(momentDate))
      return "!text-mti-red-light  font-bold line-through";
    if (today.add(1, "weeks").isAfter(momentDate)) return "!text-mti-red-light";
    if (today.add(2, "weeks").isAfter(momentDate))
      return "!text-mti-rose-light";
    if (today.add(1, "months").isAfter(momentDate))
      return "!text-mti-orange-light";
  };

  const allowedUsers = useMemo(
    () =>
      users.filter((u) => {
        if (isAdmin) return true;
        if (u.id === user?.id) return false;

        switch (u.type) {
          case "student":
            return mapBy(u.entities || [], "id").some((id) =>
              mapBy(entitiesViewStudents, "id").includes(id)
            );
          case "teacher":
            return mapBy(u.entities || [], "id").some((id) =>
              mapBy(entitiesViewTeachers, "id").includes(id)
            );
          case "corporate":
            return mapBy(u.entities || [], "id").some((id) =>
              mapBy(entitiesViewCorporates, "id").includes(id)
            );
          case "mastercorporate":
            return mapBy(u.entities || [], "id").some((id) =>
              mapBy(entitiesViewMasterCorporates, "id").includes(id)
            );
          default:
            return false;
        }
      }),
    [
      entitiesViewCorporates,
      entitiesViewMasterCorporates,
      entitiesViewStudents,
      entitiesViewTeachers,
      isAdmin,
      user?.id,
      users,
    ]
  );

  const displayUsers = useMemo(
    () =>
      filters.length > 0
        ? filters.reduce((d, f) => d.filter(f), allowedUsers)
        : allowedUsers,
    [filters, allowedUsers]
  );

  const deleteAccount = (user: User) => {
    if (!confirm(`Are you sure you want to delete ${user.name}'s account?`))
      return;

    axios
      .delete<{ ok: boolean }>(`/api/user?id=${user.id}`)
      .then(() => {
        toast.success("User deleted successfully!");
        reload();
      })
      .catch(() => {
        toast.error("Something went wrong!", { toastId: "delete-error" });
      })
      .finally(reload);
  };

  const verifyAccount = (user: User) => {
    axios
      .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
        ...user,
        isVerified: true,
      })
      .then(() => {
        toast.success("User verified successfully!");
        reload();
      })
      .catch(() => {
        toast.error("Something went wrong!", { toastId: "update-error" });
      });
  };

  const toggleDisableAccount = (user: User) => {
    if (
      !confirm(
        `Are you sure you want to ${
          user.status === "disabled" ? "enable" : "disable"
        } ${
          user.name
        }'s account? This change is usually related to their payment state.`
      )
    )
      return;

    axios
      .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
        ...user,
        status: user.status === "disabled" ? "active" : "disabled",
      })
      .then(() => {
        toast.success(
          `User ${
            user.status === "disabled" ? "enabled" : "disabled"
          } successfully!`
        );
        reload();
      })
      .catch(() => {
        toast.error("Something went wrong!", { toastId: "update-error" });
      });
  };

  const getEditPermission = (type: Type) => {
    if (type === "student") return entitiesEditStudents;
    if (type === "teacher") return entitiesEditTeachers;
    if (type === "corporate") return entitiesEditCorporates;
    if (type === "mastercorporate") return entitiesEditMasterCorporates;

    return [];
  };

  const getDeletePermission = (type: Type) => {
    if (type === "student") return entitiesDeleteStudents;
    if (type === "teacher") return entitiesDeleteTeachers;
    if (type === "corporate") return entitiesDeleteCorporates;
    if (type === "mastercorporate") return entitiesDeleteMasterCorporates;

    return [];
  };

  const canEditUser = (u: User) =>
    isAdmin ||
    u.entities.some((e) =>
      mapBy(getEditPermission(u.type), "id").includes(e.id)
    );

  const canDeleteUser = (u: User) =>
    isAdmin ||
    u.entities.some((e) =>
      mapBy(getDeletePermission(u.type), "id").includes(e.id)
    );

  const actionColumn = ({ row }: { row: { original: User } }) => {
    const canEdit = canEditUser(row.original);
    const canDelete = canDeleteUser(row.original);

    return (
      <div className="flex gap-4">
        {!row.original.isVerified && canEdit && (
          <div
            data-tip="Verify User"
            className="cursor-pointer tooltip"
            onClick={() => verifyAccount(row.original)}
          >
            <BsCheck className="hover:text-mti-purple-light transition ease-in-out duration-300" />
          </div>
        )}
        {canEdit && (
          <div
            data-tip={
              row.original.status === "disabled"
                ? "Enable User"
                : "Disable User"
            }
            className="cursor-pointer tooltip"
            onClick={() => toggleDisableAccount(row.original)}
          >
            {row.original.status === "disabled" ? (
              <BsCheckCircle className="hover:text-mti-purple-light transition ease-in-out duration-300" />
            ) : (
              <BsFillExclamationOctagonFill className="hover:text-mti-purple-light transition ease-in-out duration-300" />
            )}
          </div>
        )}
        {canDelete && (
          <div
            data-tip="Delete"
            className="cursor-pointer tooltip"
            onClick={() => deleteAccount(row.original)}
          >
            <BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
          </div>
        )}
      </div>
    );
  };

  const demographicColumns = [
    columnHelper.accessor("name", {
      header: "Name",
      cell: ({ row, getValue }) => (
        <div
          className={clsx(
            canEditUser(row.original) &&
              "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer"
          )}
          onClick={() =>
            canEditUser(row.original) ? setSelectedUser(row.original) : null
          }
        >
          {getValue()}
        </div>
      ),
    }),
    columnHelper.accessor("demographicInformation.country", {
      header: "Country",
      cell: (info) =>
        info.getValue()
          ? `${
              countryCodes.findOne("countryCode" as any, info.getValue())?.flag
            } ${
              countries[info.getValue() as unknown as keyof TCountries]?.name
            } (+${
              countryCodes.findOne("countryCode" as any, info.getValue())
                ?.countryCallingCode
            })`
          : "N/A",
    }),
    columnHelper.accessor("demographicInformation.phone", {
      header: "Phone",
      cell: (info) => info.getValue() || "N/A",
      enableSorting: true,
    }),
    columnHelper.accessor(
      (x) =>
        x.type === "corporate" || x.type === "mastercorporate"
          ? x.demographicInformation?.position
          : x.demographicInformation?.employment,
      {
        id: "employment",
        header: "Employment",
        cell: (info) =>
          (info.row.original.type === "corporate"
            ? info.getValue()
            : capitalize(info.getValue())) || "N/A",
        enableSorting: true,
      }
    ),
    columnHelper.accessor("lastLogin", {
      header: "Last Login",
      cell: (info) =>
        !!info.getValue()
          ? moment(info.getValue()).format("YYYY-MM-DD HH:mm")
          : "N/A",
    }),
    columnHelper.accessor("demographicInformation.gender", {
      header: "Gender",
      cell: (info) => capitalize(info.getValue()) || "N/A",
      enableSorting: true,
    }),
    {
      header: (
        <span
          className="cursor-pointer"
          onClick={() => setShowDemographicInformation((prev) => !prev)}
        >
          Switch
        </span>
      ),
      id: "actions",
      cell: actionColumn,
      sortable: false,
    },
  ];

  const defaultColumns = [
    columnHelper.accessor("name", {
      header: "Name",
      cell: ({ row, getValue }) => (
        <div
          className={clsx(
            canEditUser(row.original) &&
              "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer"
          )}
          onClick={() =>
            canEditUser(row.original) ? setSelectedUser(row.original) : null
          }
        >
          {getValue()}
        </div>
      ),
    }),
    columnHelper.accessor("email", {
      header: "E-mail",
      cell: ({ row, getValue }) => (
        <div
          className={clsx(
            canEditUser(row.original) &&
              "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer"
          )}
          onClick={() =>
            canEditUser(row.original) ? setSelectedUser(row.original) : null
          }
        >
          {getValue()}
        </div>
      ),
    }),
    columnHelper.accessor("type", {
      header: "Type",
      cell: (info) => USER_TYPE_LABELS[info.getValue()],
    }),
    columnHelper.accessor("studentID", {
      header: "Student ID",
      cell: (info) => info.getValue() || "N/A",
    }),
    columnHelper.accessor("entities", {
      header: "Entities",
      cell: ({ getValue }) => mapBy(getValue(), "label").join(", "),
    }),
    columnHelper.accessor("subscriptionExpirationDate", {
      header: "Expiration",
      cell: (info) => (
        <span
          className={clsx(
            info.getValue()
              ? expirationDateColor(moment(info.getValue()).toDate())
              : ""
          )}
        >
          {!info.getValue()
            ? "No expiry date"
            : moment(info.getValue()).format("DD/MM/YYYY")}
        </span>
      ),
    }),
    columnHelper.accessor("isVerified", {
      header: "Verified",
      cell: (info) => (
        <div className="flex gap-3 items-center text-mti-gray-dim text-sm self-center">
          <div
            className={clsx(
              "w-6 h-6 rounded-md flex items-center justify-center border border-mti-purple-light bg-white",
              "transition duration-300 ease-in-out",
              info.getValue() && "!bg-mti-purple-light "
            )}
          >
            <BsCheck color="white" className="w-full h-full" />
          </div>
        </div>
      ),
    }),
    {
      header: (
        <span
          className="cursor-pointer"
          onClick={() => setShowDemographicInformation((prev) => !prev)}
        >
          Switch
        </span>
      ),
      id: "actions",
      cell: actionColumn,
      sortable: false,
    },
  ];

  const downloadExcel = (rows: WithLabeledEntities<User>[]) => {
    if (entitiesDownloadUsers.length === 0)
      return toast.error("You are not allowed to download the user list.");

    const allowedRows = rows.filter((r) =>
      mapBy(r.entities, "id").some((e) =>
        mapBy(entitiesDownloadUsers, "id").includes(e)
      )
    );
    const csv = exportListToExcel(allowedRows);

    const element = document.createElement("a");
    const file = new Blob([csv], { type: "text/csv" });
    element.href = URL.createObjectURL(file);
    element.download = "users.csv";
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
  };

  const viewStudentFilter = (x: User) => x.type === "student";
  const viewTeacherFilter = (x: User) => x.type === "teacher";
  const belongsToAdminFilter = (x: User) =>
    x.entities.some(({ id }) =>
      mapBy(selectedUser?.entities || [], "id").includes(id)
    );

  const viewStudentFilterBelongsToAdmin = (x: User) =>
    viewStudentFilter(x) && belongsToAdminFilter(x);
  const viewTeacherFilterBelongsToAdmin = (x: User) =>
    viewTeacherFilter(x) && belongsToAdminFilter(x);

  const renderUserCard = (selectedUser: User) => {
    const studentsFromAdmin = users.filter(viewStudentFilterBelongsToAdmin);
    const teachersFromAdmin = users.filter(viewTeacherFilterBelongsToAdmin);
    return (
      <div className="w-full flex flex-col gap-8">
        <UserCard
          maxUserAmount={0}
          loggedInUser={user}
          onViewStudents={
            (selectedUser.type === "corporate" ||
              selectedUser.type === "teacher") &&
            studentsFromAdmin.length > 0
              ? () => {
                  appendUserFilters({
                    id: "view-students",
                    filter: viewStudentFilter,
                  });
                  appendUserFilters({
                    id: "belongs-to-admin",
                    filter: belongsToAdminFilter,
                  });

                  router.push("/users");
                }
              : undefined
          }
          onViewTeachers={
            (selectedUser.type === "corporate" ||
              selectedUser.type === "student") &&
            teachersFromAdmin.length > 0
              ? () => {
                  appendUserFilters({
                    id: "view-teachers",
                    filter: viewTeacherFilter,
                  });
                  appendUserFilters({
                    id: "belongs-to-admin",
                    filter: belongsToAdminFilter,
                  });

                  router.push("/users");
                }
              : undefined
          }
          onViewCorporate={
            selectedUser.type === "teacher" || selectedUser.type === "student"
              ? () => {
                  appendUserFilters({
                    id: "view-corporate",
                    filter: (x: User) => x.type === "corporate",
                  });
                  appendUserFilters({
                    id: "belongs-to-admin",
                    filter: belongsToAdminFilter,
                  });

                  router.push("/users");
                }
              : undefined
          }
          onClose={(shouldReload) => {
            setSelectedUser(undefined);
            if (shouldReload) reload();
          }}
          user={selectedUser}
        />
      </div>
    );
  };

  return (
    <>
      {renderHeader && renderHeader(displayUsers.length)}
      <div className="w-full">
        <Modal
          isOpen={!!selectedUser}
          onClose={() => setSelectedUser(undefined)}
        >
          {selectedUser && renderUserCard(selectedUser)}
        </Modal>
        <Table<WithLabeledEntities<User>>
          data={displayUsers}
          columns={
            (!showDemographicInformation
              ? defaultColumns
              : demographicColumns) as any
          }
          searchFields={searchFields}
          onDownload={
            entitiesDownloadUsers.length > 0 ? downloadExcel : undefined
          }
          isLoading={isLoading}
        />
      </div>
    </>
  );
}
