import { authClient } from "@code/auth/web";
import { api } from "@code/backend/convex/_generated/api";
import type { Id } from "@code/backend/convex/_generated/dataModel";
import { useAction, useMutation, useQuery } from "convex/react";
import { makeFunctionReference } from "convex/server";
import { useMemo, useState } from "react";

import { useOrganizationChatAgent } from "@/hooks/chat/use-chat-agent";
import { usePersonalOrganization } from "@/hooks/use-personal-organization";
import type { WorkRecord, WorkspaceState } from "@/lib/workspace/types";

const toError = (error: unknown) =>
  error instanceof Error ? error : new Error(String(error));

type ExecutionScenario =
  | "success"
  | "transient-failure-then-success"
  | "needs-input"
  | "permanent-failure"
  | "cancelled";

const workListRef = makeFunctionReference<
  "query",
  { projectId: Id<"projects"> },
  WorkRecord[]
>("workPlanning:listForProject");
const requestDefinitionRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works"> },
  unknown
>("workPlanning:requestDefinition");
const saveDefinitionRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; payloadJson: string },
  unknown
>("workPlanning:saveDefinitionProposal");
const approveDefinitionRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; version: number },
  unknown
>("workPlanning:approveDefinition");
const saveDesignRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; payloadJson: string },
  unknown
>("workPlanning:saveDesignProposal");
const approveDesignRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; definitionVersion: number; designVersion: number },
  unknown
>("workPlanning:approveDesign");
const startSimulationRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; scenario: ExecutionScenario; sliceId?: string },
  unknown
>("workExecution:startSimulatedExecution");
const cancelSimulationRef = makeFunctionReference<
  "mutation",
  { runId: Id<"workRuns"> },
  unknown
>("workExecution:cancelSimulatedExecution");
const retrySimulationRef = makeFunctionReference<
  "mutation",
  { runId: Id<"workRuns"> },
  unknown
>("workExecution:retrySimulatedExecution");
const startExecutionRef = makeFunctionReference<
  "mutation",
  { workId: Id<"works">; sliceId?: string },
  unknown
>("workExecutionWorkflow:startExecution");
const cancelExecutionRef = makeFunctionReference<
  "mutation",
  { runId: Id<"workRuns"> },
  unknown
>("workExecutionWorkflow:cancelExecution");
const listGitConnectionsRef = makeFunctionReference<
  "query",
  Record<string, never>,
  readonly {
    id: string;
    provider: "github" | "gitea";
    serverUrl: string;
    username?: string;
  }[]
>("gitConnectionData:list");
const projectGitConnectionRef = makeFunctionReference<
  "query",
  { projectId: Id<"projects"> },
  {
    id: string;
    provider: "github" | "gitea";
    serverUrl: string;
    username?: string;
  } | null
>("gitConnectionData:getForProject");
const connectGiteaRef = makeFunctionReference<
  "action",
  { serverUrl: string; token: string; username?: string },
  { connectionId: Id<"gitConnections"> }
>("gitConnections:connectGitea");
const connectGithubRef = makeFunctionReference<
  "action",
  Record<string, never>,
  { connectionId: Id<"gitConnections"> }
>("gitConnections:connectGithub");
const attachGitConnectionRef = makeFunctionReference<
  "mutation",
  { connectionId: Id<"gitConnections">; projectId: Id<"projects"> },
  unknown
>("gitConnectionData:attachToProject");

export const useProjectWorkspace = (): WorkspaceState => {
  const organization = usePersonalOrganization();
  const projects = useQuery(
    api.projects.list,
    organization.organizationId ? {} : "skip"
  );
  const importPublicGit = useAction(api.projects.importPublicGit);
  const agent = useOrganizationChatAgent(organization);
  const [selectedProjectId, setSelectedProjectId] =
    useState<Id<"projects"> | null>(null);
  const [repository, setRepository] = useState("");
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<Error>();
  const [operationError, setOperationError] = useState<Error>();

  const selectedProjectStillExists = projects?.some(
    (project) => project.id === (selectedProjectId as unknown as string)
  );
  const activeProjectId = selectedProjectStillExists
    ? selectedProjectId
    : ((projects?.[0]?.id as unknown as Id<"projects"> | undefined) ?? null);
  const works = useQuery(
    workListRef,
    activeProjectId ? { projectId: activeProjectId } : "skip"
  );
  const gitConnections = useQuery(
    listGitConnectionsRef,
    organization.organizationId ? {} : "skip"
  );
  const projectGitConnection = useQuery(
    projectGitConnectionRef,
    activeProjectId ? { projectId: activeProjectId } : "skip"
  );
  const requestDefinitionMutation = useMutation(requestDefinitionRef);
  const saveDefinitionMutation = useMutation(saveDefinitionRef);
  const approveDefinitionMutation = useMutation(approveDefinitionRef);
  const saveDesignMutation = useMutation(saveDesignRef);
  const approveDesignMutation = useMutation(approveDesignRef);
  const startSimulationMutation = useMutation(startSimulationRef);
  const cancelSimulationMutation = useMutation(cancelSimulationRef);
  const retrySimulationMutation = useMutation(retrySimulationRef);
  const startExecutionMutation = useMutation(startExecutionRef);
  const cancelExecutionMutation = useMutation(cancelExecutionRef);
  const attachGitConnectionMutation = useMutation(attachGitConnectionRef);
  const connectGiteaAction = useAction(connectGiteaRef);
  const connectGithubAction = useAction(connectGithubRef);
  const selectedProject = useMemo(
    () =>
      projects?.find(
        (project) => project.id === (activeProjectId as unknown as string)
      ) ?? null,
    [activeProjectId, projects]
  );

  const runOperation = async <T>(operation: () => Promise<T>): Promise<T> => {
    setOperationError(undefined);
    try {
      return await operation();
    } catch (caughtError) {
      setOperationError(toError(caughtError));
      throw caughtError;
    }
  };

  const connectRepository = async () => {
    const repositoryUrl = repository.trim();
    if (!repositoryUrl || pending) {
      return;
    }
    setPending(true);
    setError(undefined);
    try {
      const project = await importPublicGit({ repositoryUrl });
      setSelectedProjectId(project.id as unknown as Id<"projects">);
      setRepository("");
    } catch (caughtError) {
      setError(toError(caughtError));
    } finally {
      setPending(false);
    }
  };

  const attachConnection = async (connectionId: Id<"gitConnections">) => {
    if (!activeProjectId) {
      throw new Error("Select a project first");
    }
    await attachGitConnectionMutation({
      connectionId,
      projectId: activeProjectId,
    });
  };

  const connectGitea = (input: {
    serverUrl: string;
    token: string;
    username?: string;
  }) =>
    runOperation(async () => {
      const result = await connectGiteaAction(input);
      await attachConnection(result.connectionId);
    });

  const connectLinkedGithub = () =>
    runOperation(async () => {
      const result = await connectGithubAction({});
      await attachConnection(result.connectionId);
    });

  return {
    agent,
    approveDefinition: (workId: Id<"works">, version: number) =>
      approveDefinitionMutation({ version, workId }),
    approveDesign: (
      workId: Id<"works">,
      definitionVersion: number,
      designVersion: number
    ) => approveDesignMutation({ definitionVersion, designVersion, workId }),
    authorizeGithub: () =>
      authClient.signIn.social({
        callbackURL: window.location.href,
        provider: "github",
      }),
    cancelExecution: (runId: Id<"workRuns">) =>
      runOperation(() => cancelExecutionMutation({ runId })),
    cancelSimulation: (runId: Id<"workRuns">) =>
      runOperation(() => cancelSimulationMutation({ runId })),
    clearOperationError: () => setOperationError(undefined),
    connectGitea,
    connectLinkedGithub,
    connectRepository,
    error,
    gitConnections,
    operationError,
    pending,
    projectGitConnection,
    projects,
    repository,
    requestDefinition: (workId: Id<"works">) =>
      requestDefinitionMutation({ workId }),
    retrySimulation: (runId: Id<"workRuns">) =>
      runOperation(() => retrySimulationMutation({ runId })),
    saveDefinition: (workId: Id<"works">, payload: unknown) =>
      saveDefinitionMutation({ payloadJson: JSON.stringify(payload), workId }),
    saveDesign: (workId: Id<"works">, payload: unknown) =>
      saveDesignMutation({ payloadJson: JSON.stringify(payload), workId }),
    selectProject: (projectId: string) =>
      setSelectedProjectId(projectId as unknown as Id<"projects">),
    selectedProject: selectedProject
      ? { id: selectedProject.id, name: selectedProject.name }
      : null,
    setRepository,
    startExecution: (workId: Id<"works">, sliceId?: string) =>
      runOperation(() => startExecutionMutation({ sliceId, workId })),
    startSimulation: (
      workId: Id<"works">,
      scenario: ExecutionScenario,
      sliceId?: string
    ) =>
      runOperation(() =>
        startSimulationMutation({ scenario, sliceId, workId })
      ),
    works,
  };
};
