import { useMutation, useQuery, useSubscription } from "@apollo/client";
import { Link, useResponse, useRouter } from "@curi/react-dom";
import { faPlayCircle, faVial } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  Backdrop,
  CircularProgress,
  Collapse,
  IconButton,
  LinearProgress,
  List,
  ListItem,
  ListItemIcon,
  ListItemSecondaryAction,
  ListItemText,
  makeStyles,
  useTheme,
  withStyles,
} from "@material-ui/core";
import {
  Cancel,
  CheckCircle,
  CloudDownload,
  ShoppingCart,
  Stop,
  Timer,
} from "@material-ui/icons";
import { format } from "date-fns";
import { useSnackbar } from "notistack";
import { complement, equals, find, propEq, takeWhile } from "ramda";
import {
  FC,
  Fragment,
  MouseEventHandler,
  ReactNode,
  useCallback,
  useMemo,
  useState,
} from "react";
import {
  Commit,
  CreatePipelineTaskInput,
  Pipeline,
  PipelineTask,
  TaskStatuses,
  PipelineUnits,
} from "../generated/graphql";
import { CREATE_PIPELINE_TASK, STOP_PIPELINE_TASK } from "./mutations";
import { COMMITS } from "./queries";
import { SYNC_COMMITS } from "./subscriptions";

interface Props {
  pipeline: Pipeline;
}

const useStyles = makeStyles((theme) => ({
  root: {
    flex: "1 1 100%",
  },
  nested: {
    paddingLeft: theme.spacing(4),
  },
}));

export const CommitList: FC<Props> = ({ pipeline }) => {
  const { enqueueSnackbar } = useSnackbar();

  const { data, loading, refetch } = useQuery<{ commits?: Commit[] }>(COMMITS, {
    variables: {
      pipelineId: pipeline.id,
    },
  });

  const { loading: syncing } = useSubscription<{ syncCommits: boolean }>(
    SYNC_COMMITS,
    {
      variables: {
        pipelineId: pipeline.id,
      },
      onSubscriptionData({ subscriptionData: { data, error } }) {
        if (error) {
          enqueueSnackbar(error.message, {
            variant: "warning",
          });
        }
        if (data?.syncCommits) {
          refetch({
            appInstance: data.syncCommits,
          });
        }
      },
    }
  );

  const classes = useStyles();

  return (
    <section className={classes.root}>
      {(() => {
        if (loading) {
          return <LinearProgress color="secondary" />;
        }

        return (
          <section>
            {syncing && <LinearProgress color="secondary" />}
            <List>
              {data?.commits?.map((commit) => (
                <Item key={commit.hash} commit={commit} pipeline={pipeline} />
              ))}
            </List>
          </section>
        );
      })()}
    </section>
  );
};

const unitActionPairs: Array<[PipelineUnits, ReactNode, string]> = [
  [PipelineUnits.Checkout, <ShoppingCart />, "checkout"],
  [
    PipelineUnits.InstallDependencies,
    <CloudDownload />,
    "install dependencies",
  ],
  [PipelineUnits.Test, <FontAwesomeIcon icon={faVial} />, "test"],
  [PipelineUnits.Deploy, <FontAwesomeIcon icon={faPlayCircle} />, "deploy"],
];

const Item: FC<{ commit: Commit; pipeline: Pipeline }> = ({
  commit,
  pipeline,
}) => {
  const [isOpen, setOpen] = useState(() => false);

  const [createTask, { loading }] =
    useMutation<
      { createPipelineTask: PipelineTask },
      { task: CreatePipelineTaskInput }
    >(CREATE_PIPELINE_TASK);

  const units = useMemo(
    () => pipeline.workUnitMetadata.units.map((unit) => unit.type),
    [pipeline]
  );

  const { navigate, url } = useRouter();
  const { response } = useResponse();

  const handleCreateTask = useCallback(
    (unit: PipelineUnits) => {
      const _units = [...takeWhile(complement(equals(unit)), units), unit];
      createTask({
        variables: {
          task: {
            units: _units,
            pipelineId: pipeline.id,
            commit: commit.hash,
          },
        },
      }).then(({ data }) => {
        navigate({
          url: url({
            name: "pipeline-task-detail",
            params: { ...response.params, taskId: data?.createPipelineTask.id },
          }),
        });
      });
    },
    [commit, createTask, navigate, pipeline, response, units, url]
  );

  const actions = useMemo(
    () =>
      units.map((unit) => {
        const pair = find(propEq(0, unit), unitActionPairs);
        return (
          pair && (
            <IconButton
              key={unit}
              aria-label={pair[2]}
              disabled={loading}
              onClick={() => handleCreateTask(unit)}
            >
              {pair[1]}
            </IconButton>
          )
        );
      }),
    [units, handleCreateTask, loading]
  );
  return (
    <Fragment>
      <ListItem button onClick={() => setOpen(!isOpen)}>
        <ListItemText
          primary={commit.message}
          secondary={commit.date && format(commit.date, "yyyy-MM-dd HH:mm:ss")}
        />
        <ListItemSecondaryAction>{actions}</ListItemSecondaryAction>
      </ListItem>
      {loading && <LinearProgress color="secondary" />}
      <Collapse in={isOpen} timeout="auto" unmountOnExit>
        <List component="div" disablePadding>
          {commit.tasks.map((task) => (
            <Link
              key={task.id}
              name="pipeline-task-detail"
              params={{ ...response.params, taskId: task.id }}
            >
              <TaskItem task={task} />
            </Link>
          ))}
        </List>
      </Collapse>
    </Fragment>
  );
};

const TaskItem: FC<{ task: PipelineTask }> = ({ task }) => {
  const classes = useStyles();
  const theme = useTheme();
  const statusIcon: ReactNode = (() => {
    switch (task.status) {
      case TaskStatuses.Pending:
        return <Timer style={{ color: theme.palette.info.main }} />;
      case TaskStatuses.Success:
        return <CheckCircle style={{ color: theme.palette.success.main }} />;
      case TaskStatuses.Failed:
        return <Cancel style={{ color: theme.palette.error.main }} />;
      case TaskStatuses.Working:
        return (
          <CircularProgress style={{ color: theme.palette.secondary.main }} />
        );
    }
  })();
  const [stopTask, { loading: stopTaskWaiting }] = useMutation(
    STOP_PIPELINE_TASK,
    {
      variables: { taskId: task.id },
    }
  );
  const stop: MouseEventHandler = useCallback(
    (event) => {
      event.stopPropagation();
      event.preventDefault();
      stopTask();
    },
    [stopTask]
  );
  return (
    <ListItem button className={classes.nested}>
      <ListItemIcon>{statusIcon}</ListItemIcon>
      <ListItemText
        primary={
          task.startedAt && format(task.startedAt, "yyyy-MM-dd HH:mm:ss")
        }
      />
      <ListItemSecondaryAction>
        {task.status === TaskStatuses.Working && (
          <IconButton edge="end" aria-label="stop" onClick={stop}>
            <Stop />
          </IconButton>
        )}
      </ListItemSecondaryAction>
      <LimitedBackdrop open={stopTaskWaiting} />
    </ListItem>
  );
};
const LimitedBackdrop = withStyles({
  root: {
    position: "absolute",
    zIndex: 1,
  },
})(Backdrop);
