import React, { useReducer, createContext, useMemo } from "react";
import Initiator from "@qltr/initiator";
const JobContext = createContext();

export const jobStatus = {
  OK: "o",
  QUEUED: "q",
  PENDING: "p",
  CANCELED: "c",
  ACTIVE: "a",
  ERROR: "e",
};

const ACTIONS = {
  CREATE: "c",
  UPDATE: "u",
  DELETE: "d",
  PIPELINE: "p",
};

const url = "https://qualiteer.elijahparker3.repl.co/";

const initialState = {
  jobs: [],
  pipelines: [],
};

const reducer = (state, action) => {
  // Current Jobs
  const { jobs, pipelines } = state;
  var jobIndex;
  // Actions
  switch (action.type) {
    case ACTIONS.CREATE:
      jobs.push(action.job);
      return { ...state, jobs };

    case ACTIONS.UPDATE:
      jobIndex = jobs.findIndex(
        (j) => j.jobId === (action.job.jobId ?? action.jobId)
      );
      jobs[jobIndex] = { ...jobs[jobIndex], ...action.job };
      return { ...state, jobs };

    case ACTIONS.DELETE:
      jobIndex = jobs.findIndex((j) => j.jobId === action.jobId);
      jobs.splice(jobIndex, 1);
      return { ...state, jobs };

    case ACTIONS.PIPELINE:
      return { ...state, pipelines };
    default:
      return state;
  }
};

export const JobProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const jobUpdate = (job, jobId) =>
    dispatch({ type: ACTIONS.UPDATE, jobId, job });
  const jobCreate = (job) =>
    dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
  const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });

  const updatePipelines = (pipelines) =>
    dispatch({ type: ACTIONS.pipeline, pipelines });

  function retryAll(failing) {
    // Query Full Locator
    console.log("Would retry all failing tests!");
    return jobFactory({ testNames: ["single"] });
  }

  function pipelineComponentJob(jobPipeline, pipelineReq) {
    const i = new Initiator(url);
    const jobId = `j${Date.now()}`;
    const job = {
      name: jobId,
      status: jobStatus.PENDING,
      jobId,
      isPipeline: true,
      initiator: i,
      pipelineId: jobPipeline.id,
      branchId: pipelineReq.pipeline.__test,
    };
    const request = {
      image: "node",
      name: jobId,
      ...pipelineReq,
    };

    jobCreate(job);
    const onLog = (d) => {
      const job = state.jobs.find((j) => j.jobId === jobId);

      job.log.push(d);
      job.status = jobStatus.ACTIVE;
      jobUpdate({ ...job }, jobId);
    };

    const onClose = (c) => {
      const job = state.jobs.find((j) => j.jobId === jobId);
      job.exitcode = c;
      job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
      jobUpdate({ ...job }, jobId);
    };

    const onPipelineTrigger = (p) => {
      const { triggers } = p;
      for (var t in triggers) {
        const delay = triggers[t].__testDelay ?? 0;
        delete triggers[t].__testDelay;
        const jobReq = {
          ...request,
          pipeline: {
            ...p,
            triggers: triggers[t],
            __test: t,
          },
        };
        jobPipeline.pendingTriggers.push({
          testName: t,
          timer: setTimeout(
            () => pipelineComponentJob(jobPipeline, jobReq),
            delay
          ),
          triggerAt: Date.now() + delay,
        });
      }
    };
    const started = i.newPipelineJob(
      request,
      onLog,
      onClose,
      null,
      onPipelineTrigger
    );
    started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
  }

  function pipelineFactory(builderCache) {
    const { tree, branches, selectedBranches } = builderCache;
    const __test = Object.keys(tree)[0];
    const pipelineReq = {
      image: "node",
      pipeline: { __test, triggers: { ...tree[__test] } },
    };
    const id = `pij${Date.now()}`;
    const pipeline = { id, branches, pendingTriggers: [], selectedBranches };
    const { pipelines } = state;
    pipelines.push(pipeline);
    updatePipelines([...pipelines]);
    pipelineComponentJob(pipeline, pipelineReq);
    return pipeline;
  }

  function pipelineCancel(pipelineId) {
    const pipeline = state.pipelines.find((p) => p.id === pipelineId);
    pipeline.isCanceled = true;
    pipeline.pendingTriggers.forEach((t) => clearTimeout(t));
    const jobs = state.jobs.filter(
      (j) => j.isPipeline && j.pipelineId === pipelineId
    );
    for (var j of jobs) {
      if (j.initiator.sk) j.initiator.sk.close();
      j.status = jobStatus.CANCELED;
      jobUpdate({ ...j }, j.jobId);
    }
  }

  function pipelineDestroy(pipelineId) {
    const pipelineIndex = state.pipelines.findIndex((p) => p.id === pipelineId);
    const pipeline = state.pipelines[pipelineIndex];
    pipeline.pendingTriggers.forEach((t) => clearTimeout(t));
    const jobs = state.jobs.filter(
      (j) => j.isPipeline && j.pipelineId === pipelineId
    );
    for (var j of jobs) {
      if (
        j.initiator.sk &&
        j.status !== jobStatus.OK &&
        j.status !== jobStatus.ERROR &&
        j.status !== jobStatus.CANCELED
      ) {
        j.initiator.sk.close();
      }
      jobDelete(j.jobId);
    }
    state.pipelines.splice(pipelineIndex, 1);
  }

  function jobCancel(jobId) {
    const job = state.jobs.find((j) => j.jobId === jobId);

    if (job.initiator.sk) job.initiator.sk.close();
    job.status = jobStatus.CANCELED;
    jobUpdate({ ...job }, jobId);
  }

  function jobDestroy(jobId) {
    const job = state.jobs.find((j) => j.jobId === jobId);

    if (
      job.initiator.sk &&
      job.status !== jobStatus.OK &&
      job.status !== jobStatus.ERROR &&
      job.status !== jobStatus.CANCELED
    ) {
      job.initiator.sk.close();
    }
    jobDelete(jobId);
  }

  function jobFactory(builderCache) {
    if (builderCache.tree) return pipelineFactory(builderCache);
    // Find test
    const i = new Initiator(url);
    const jobId = `j${Date.now()}`;
    const job = {
      name: jobId,
      status: jobStatus.PENDING,
      jobId,
      isPipeline: false,
      builderCache,
      initiator: i,
    };

    const request = {
      testNames: builderCache.testNames,
      image: "node",
      type: "single",
      name: jobId,
    };

    jobCreate(job);

    const onLog = (d) => {
      const job = state.jobs.find((j) => j.jobId === jobId);
      job.log.push(d);
      job.status = jobStatus.ACTIVE;
      jobUpdate({ ...job }, jobId);
    };

    const onClose = (c) => {
      const job = state.jobs.find((j) => j.jobId === jobId);
      job.exitcode = c;
      job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
      jobUpdate({ ...job }, jobId);
    };

    const started = i.newJob(request, onLog, onClose);
    started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));

    return jobId;
  }

  const context = {
    state,
    dispatch,
    jobUpdate,
    jobCreate,
    jobDelete,
    retryAll,
    jobFactory,
    jobCancel,
    jobDestroy,
    pipelineCancel,
    pipelineDestroy,
  };
  const contextValue = useMemo(() => context, [state, dispatch]);

  return (
    <JobContext.Provider value={contextValue}>{children}</JobContext.Provider>
  );
};

export default JobContext;
