import { Add, Check, Close, Delete } from "@mui/icons-material";
import { Box, Button, Tooltip, Typography } from "@mui/material";
import {
  FooterPropsOverrides,
  GridActionsCellItem,
  GridCellParams,
  GridColDef,
  GridEditInputCell,
  GridEventListener,
  GridRenderEditCellParams,
  GridRowId,
  GridRowModel,
  GridRowModes,
  GridRowModesModel,
  GridToolbarContainer,
  useGridApiRef,
} from "@mui/x-data-grid";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useFormContext } from "react-hook-form";
import { RecordTimesheetInput, TimeEntry } from "@/app/api/timesheets/actions";
import { manhourFormatter } from "@/app/utils/formatUtil";
import { AssignedProject, ProjectWithTasks } from "@/app/api/projects";
import uniqBy from "lodash/uniqBy";
import { TaskGroup } from "@/app/api/tasks";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import ProjectSelect from "./ProjectSelect";
import TaskGroupSelect from "./TaskGroupSelect";
import TaskSelect from "./TaskSelect";
import {
  DAILY_NORMAL_MAX_HOURS,
  TimeEntryError,
  validateTimeEntry,
} from "@/app/api/timesheets/utils";
import { roundToNearestQuarter } from "@/app/utils/manhourUtils";
import FastTimeEntryModal from "./FastTimeEntryModal";

dayjs.extend(isBetween);

interface Props {
  day: string;
  isHoliday: boolean;
  allProjects: ProjectWithTasks[];
  assignedProjects: AssignedProject[];
  fastEntryEnabled?: boolean;
}

export type TimeEntryRow = Partial<
  TimeEntry & {
    _isNew: boolean;
    _error: TimeEntryError;
    isPlanned?: boolean;
  }
>;

const EntryInputTable: React.FC<Props> = ({
  day,
  allProjects,
  assignedProjects,
  isHoliday,
  fastEntryEnabled,
}) => {
  const { t } = useTranslation("home");
  const taskGroupsByProject = useMemo(() => {
    return allProjects.reduce<{
      [projectId: AssignedProject["id"]]: {
        value: TaskGroup["id"];
        label: string;
      }[];
    }>((acc, project) => {
      return {
        ...acc,
        [project.id]: uniqBy(
          project.tasks.map((t) => ({
            value: t.taskGroup.id,
            label: t.taskGroup.name,
          })),
          "value",
        ),
      };
    }, {});
  }, [allProjects]);

  // To check for start / end planned dates
  const milestonesByProject = useMemo(() => {
    return assignedProjects.reduce<{
      [projectId: AssignedProject["id"]]: AssignedProject["milestones"];
    }>((acc, project) => {
      return { ...acc, [project.id]: { ...project.milestones } };
    }, {});
  }, [assignedProjects]);

  const { getValues, setValue, clearErrors } =
    useFormContext<RecordTimesheetInput>();
  const currentEntries = getValues(day);

  const [entries, setEntries] = useState<TimeEntryRow[]>(currentEntries || []);

  const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});

  const apiRef = useGridApiRef();
  const addRow = useCallback(() => {
    const id = Date.now();
    setEntries((e) => [...e, { id, _isNew: true }]);
    setRowModesModel((model) => ({
      ...model,
      [id]: { mode: GridRowModes.Edit, fieldToFocus: "projectId" },
    }));
  }, []);

  const validateRow = useCallback(
    (id: GridRowId) => {
      const row = apiRef.current.getRowWithUpdatedValues(
        id,
        "",
      ) as TimeEntryRow;

      const error = validateTimeEntry(row, isHoliday);

      // Test for warnings
      let isPlanned;
      if (
        row.projectId &&
        row.taskGroupId &&
        milestonesByProject[row.projectId]
      ) {
        const milestone =
          milestonesByProject[row.projectId][row.taskGroupId] || {};
        const { startDate, endDate } = milestone;
        // Check if the current day is between the start and end date inclusively
        isPlanned = dayjs(day).isBetween(startDate, endDate, "day", "[]");
      }

      apiRef.current.updateRows([{ id, _error: error, isPlanned }]);
      return !error;
    },
    [apiRef, day, isHoliday, milestonesByProject],
  );

  const handleCancel = useCallback(
    (id: GridRowId) => () => {
      setRowModesModel((model) => ({
        ...model,
        [id]: { mode: GridRowModes.View, ignoreModifications: true },
      }));
      const editedRow = entries.find((entry) => entry.id === id);
      if (editedRow?._isNew) {
        setEntries((es) => es.filter((e) => e.id !== id));
      }
    },
    [entries],
  );

  const handleDelete = useCallback(
    (id: GridRowId) => () => {
      setEntries((es) => es.filter((e) => e.id !== id));
    },
    [],
  );

  const handleSave = useCallback(
    (id: GridRowId) => () => {
      if (validateRow(id)) {
        setRowModesModel((model) => ({
          ...model,
          [id]: { mode: GridRowModes.View },
        }));
      }
    },
    [validateRow],
  );

  const handleEditStop = useCallback<GridEventListener<"rowEditStop">>(
    (params, event) => {
      if (!validateRow(params.id)) {
        event.defaultMuiPrevented = true;
      }
    },
    [validateRow],
  );

  const processRowUpdate = useCallback((newRow: GridRowModel) => {
    const updatedRow = { ...newRow, _isNew: false };
    setEntries((es) => es.map((e) => (e.id === newRow.id ? updatedRow : e)));
    return updatedRow;
  }, []);

  const columns = useMemo<GridColDef[]>(
    () => [
      {
        type: "actions",
        field: "actions",
        headerName: t("Actions"),
        getActions: ({ id }) => {
          if (rowModesModel[id]?.mode === GridRowModes.Edit) {
            return [
              <GridActionsCellItem
                key="accpet-action"
                icon={<Check />}
                label={t("Save")}
                onClick={handleSave(id)}
              />,
              <GridActionsCellItem
                key="cancel-action"
                icon={<Close />}
                label={t("Cancel")}
                onClick={handleCancel(id)}
              />,
            ];
          }

          return [
            <GridActionsCellItem
              key="delete-action"
              icon={<Delete />}
              label={t("Remove")}
              onClick={handleDelete(id)}
            />,
          ];
        },
      },
      {
        field: "projectId",
        headerName: t("Project Code and Name"),
        width: 300,
        editable: true,
        valueFormatter(params) {
          const project = allProjects.find((p) => p.id === params.value);
          return project ? `${project.code} - ${project.name}` : t("None");
        },
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow, number>) {
          return (
            <ProjectSelect
              multiple={false}
              allProjects={allProjects}
              assignedProjects={assignedProjects}
              value={params.value}
              onProjectSelect={(projectId) => {
                params.api.setEditCellValue({
                  id: params.id,
                  field: params.field,
                  value: projectId,
                });
                params.api.setCellFocus(params.id, "taskGroupId");
              }}
            />
          );
        },
      },
      {
        field: "taskGroupId",
        headerName: t("Stage"),
        width: 200,
        editable: true,
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow, number>) {
          return (
            <TaskGroupSelect
              projectId={params.row.projectId}
              value={params.value}
              taskGroupsByProject={taskGroupsByProject}
              onTaskGroupSelect={(taskGroupId) => {
                params.api.setEditCellValue({
                  id: params.id,
                  field: params.field,
                  value: taskGroupId,
                });
                params.api.setCellFocus(params.id, "taskId");
              }}
            />
          );
        },
        valueFormatter(params) {
          const taskGroups = params.id
            ? taskGroupsByProject[params.api.getRow(params.id).projectId] || []
            : [];
          const taskGroup = taskGroups.find((tg) => tg.value === params.value);
          return taskGroup ? taskGroup.label : t("None");
        },
      },
      {
        field: "taskId",
        headerName: t("Task"),
        width: 200,
        editable: true,
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow, number>) {
          return (
            <TaskSelect
              value={params.value}
              projectId={params.row.projectId}
              taskGroupId={params.row.taskGroupId}
              allProjects={allProjects}
              onTaskSelect={(taskId) => {
                params.api.setEditCellValue({
                  id: params.id,
                  field: params.field,
                  value: taskId,
                });
                params.api.setCellFocus(params.id, "inputHours");
              }}
            />
          );
        },
        valueFormatter(params) {
          const projectId = params.id
            ? params.api.getRow(params.id).projectId
            : undefined;

          const task = projectId
            ? allProjects
                .find((p) => p.id === projectId)
                ?.tasks.find((t) => t.id === params.value)
            : undefined;

          return task ? task.name : t("None");
        },
      },
      {
        field: "inputHours",
        headerName: t("Hours"),
        width: 100,
        editable: true,
        type: "number",
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow>) {
          const errorMessage =
            params.row._error?.[params.field as keyof TimeEntry];
          const content = <GridEditInputCell {...params} />;
          return errorMessage ? (
            <Tooltip title={t(errorMessage, { DAILY_NORMAL_MAX_HOURS })}>
              <Box width="100%">{content}</Box>
            </Tooltip>
          ) : (
            content
          );
        },
        valueParser(value) {
          return value ? roundToNearestQuarter(value) : value;
        },
        valueFormatter(params) {
          return manhourFormatter.format(params.value || 0);
        },
      },
      {
        field: "otHours",
        headerName: t("Other Hours"),
        width: 150,
        editable: true,
        type: "number",
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow>) {
          const errorMessage =
            params.row._error?.[params.field as keyof TimeEntry];
          const content = <GridEditInputCell {...params} />;
          return errorMessage ? (
            <Tooltip title={t(errorMessage)}>
              <Box width="100%">{content}</Box>
            </Tooltip>
          ) : (
            content
          );
        },
        valueParser(value) {
          return value ? roundToNearestQuarter(value) : value;
        },
        valueFormatter(params) {
          return manhourFormatter.format(params.value || 0);
        },
      },
      {
        field: "remark",
        headerName: t("Remark"),
        sortable: false,
        flex: 1,
        editable: true,
        renderEditCell(params: GridRenderEditCellParams<TimeEntryRow>) {
          const errorMessage =
            params.row._error?.[params.field as keyof TimeEntry];
          const content = <GridEditInputCell {...params} />;
          return errorMessage ? (
            <Tooltip title={t(errorMessage)}>
              <Box width="100%">{content}</Box>
            </Tooltip>
          ) : (
            content
          );
        },
      },
    ],
    [
      t,
      rowModesModel,
      handleDelete,
      handleSave,
      handleCancel,
      assignedProjects,
      allProjects,
      taskGroupsByProject,
    ],
  );

  useEffect(() => {
    setValue(day, [
      ...entries
        .filter((e) => !e._isNew && !e._error && e.id)
        .map(({ isPlanned, _error, _isNew, ...entry }) => ({
          id: entry.id!,
          ...entry,
        })),
    ]);
    clearErrors(day);
  }, [getValues, entries, setValue, day, clearErrors]);

  const hasOutOfPlannedStages = entries.some(
    (entry) => entry.isPlanned !== undefined && !entry.isPlanned,
  );

  // Fast entry modal
  const [fastEntryModalOpen, setFastEntryModalOpen] = useState(false);
  const closeFastEntryModal = useCallback(() => {
    setFastEntryModalOpen(false);
  }, []);
  const openFastEntryModal = useCallback(() => {
    setFastEntryModalOpen(true);
  }, []);
  const onSaveFastEntry = useCallback(async (entries: TimeEntry[]) => {
    setEntries((e) => [...e, ...entries]);
    setFastEntryModalOpen(false);
  }, []);

  const footer = (
    <Box display="flex" gap={2} alignItems="center">
      <Button
        disableRipple
        variant="outlined"
        startIcon={<Add />}
        onClick={addRow}
        size="small"
      >
        {t("Record time")}
      </Button>
      {fastEntryEnabled && <Button
        disableRipple
        variant="outlined"
        startIcon={<Add />}
        onClick={openFastEntryModal}
        size="small"
      >
        {t("Fast time entry")}
      </Button>}
      {hasOutOfPlannedStages && (
        <Typography color="warning.main" variant="body2">
          {t("There are entries for stages out of planned dates!")}
        </Typography>
      )}
    </Box>
  );

  return (
    <>
      <StyledDataGrid
        apiRef={apiRef}
        autoHeight
        sx={{
          "--DataGrid-overlayHeight": "100px",
          ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
            border: "1px solid",
            borderColor: "error.main",
          },
          ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
            border: "1px solid",
            borderColor: "warning.main",
          },
        }}
        disableColumnMenu
        editMode="row"
        rows={entries}
        rowModesModel={rowModesModel}
        onRowModesModelChange={setRowModesModel}
        onRowEditStop={handleEditStop}
        processRowUpdate={processRowUpdate}
        columns={columns}
        getCellClassName={(params: GridCellParams<TimeEntryRow>) => {
          let classname = "";
          if (params.row._error?.[params.field as keyof TimeEntry]) {
            classname = "hasError";
          } else if (
            params.field === "taskGroupId" &&
            params.row.isPlanned !== undefined &&
            !params.row.isPlanned
          ) {
            classname = "hasWarning";
          }
          return classname;
        }}
        slots={{
          footer: FooterToolbar,
          noRowsOverlay: NoRowsOverlay,
        }}
        slotProps={{
          footer: { child: footer },
        }}
      />
      {fastEntryEnabled && (
        <FastTimeEntryModal
          allProjects={allProjects}
          assignedProjects={assignedProjects}
          open={fastEntryModalOpen}
          isHoliday={Boolean(isHoliday)}
          onClose={closeFastEntryModal}
          onSave={onSaveFastEntry}
        />
      )}
    </>
  );
};

const NoRowsOverlay: React.FC = () => {
  const { t } = useTranslation("home");
  return (
    <Box
      display="flex"
      justifyContent="center"
      alignItems="center"
      height="100%"
    >
      <Typography variant="caption">{t("Add some time entries!")}</Typography>
    </Box>
  );
};

const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
};

export default EntryInputTable;
