import { env } from "@code/env/convex";
import { ConvexError, v } from "convex/values";

import { mutation, query } from "./_generated/server";
import { artifactPath } from "./artifactModel";

const requireAgent = (token: string) => {
  if (token !== env.FLUE_DB_TOKEN) {
    throw new ConvexError("Invalid agent control token");
  }
};

const agentStatus = v.union(
  v.literal("working"),
  v.literal("needs-input"),
  v.literal("completed"),
  v.literal("failed")
);

export const get = query({
  args: { issueId: v.id("projectIssues"), token: v.string() },
  handler: async (ctx, args) => {
    requireAgent(args.token);
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    const project = await ctx.db.get("projects", issue.projectId);
    if (!project) {
      throw new ConvexError("Project not found");
    }
    const artifacts = await ctx.db
      .query("projectArtifacts")
      .withIndex("by_project", (q) => q.eq("projectId", project._id))
      .take(25);
    return { artifacts, issue, project };
  },
});

export const ensureRun = mutation({
  args: {
    daemonId: v.string(),
    issueId: v.id("projectIssues"),
    token: v.string(),
  },
  handler: async (ctx, args) => {
    requireAgent(args.token);
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    const existing = await ctx.db
      .query("projectWorkRuns")
      .withIndex("by_issue", (q) => q.eq("issueId", issue._id))
      .unique();
    if (existing) {
      return existing;
    }
    const timestamp = Date.now();
    const actorKey = [
      "project",
      String(issue.projectId),
      "issue",
      String(issue._id),
    ];
    const runId = await ctx.db.insert("projectWorkRuns", {
      actorKey,
      agentId: String(issue._id),
      createdAt: timestamp,
      daemonId: args.daemonId,
      issueId: issue._id,
      projectId: issue.projectId,
      status: "queued",
      updatedAt: timestamp,
    });
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { actorKey, daemonId: args.daemonId },
      issueId: issue._id,
      kind: "agent.workspace.created",
      projectId: issue.projectId,
      runId,
    });
    return await ctx.db.get("projectWorkRuns", runId);
  },
});

export const updateArtifact = mutation({
  args: {
    content: v.string(),
    issueId: v.id("projectIssues"),
    path: artifactPath,
    token: v.string(),
  },
  handler: async (ctx, args) => {
    requireAgent(args.token);
    if (args.content.length > 200_000) {
      throw new ConvexError("Artifact content exceeds 200000 characters");
    }
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    const artifact = await ctx.db
      .query("projectArtifacts")
      .withIndex("by_project_and_path", (q) =>
        q.eq("projectId", issue.projectId).eq("path", args.path)
      )
      .unique();
    if (!artifact) {
      throw new ConvexError(`Artifact ${args.path} not found`);
    }
    const timestamp = Date.now();
    await ctx.db.patch("projectArtifacts", artifact._id, {
      content: args.content,
      revision: artifact.revision + 1,
      updatedAt: timestamp,
    });
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { path: args.path, revision: artifact.revision + 1 },
      issueId: issue._id,
      kind: "artifact.updated",
      projectId: issue.projectId,
    });
    return artifact.revision + 1;
  },
});

export const setStatus = mutation({
  args: {
    issueId: v.id("projectIssues"),
    status: agentStatus,
    summary: v.optional(v.string()),
    token: v.string(),
  },
  handler: async (ctx, args) => {
    requireAgent(args.token);
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    const run = await ctx.db
      .query("projectWorkRuns")
      .withIndex("by_issue", (q) => q.eq("issueId", issue._id))
      .unique();
    if (!run) {
      throw new ConvexError("Agent work run not found");
    }
    const timestamp = Date.now();
    const terminal = args.status === "completed" || args.status === "failed";
    await ctx.db.patch("projectIssues", issue._id, {
      status: args.status,
      updatedAt: timestamp,
    });
    await ctx.db.patch("projectWorkRuns", run._id, {
      status: args.status,
      summary: args.summary?.slice(0, 4000),
      updatedAt: timestamp,
      ...(args.status === "working" && run.startedAt === undefined
        ? { startedAt: timestamp }
        : {}),
      ...(terminal ? { completedAt: timestamp } : {}),
    });
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { summary: args.summary?.slice(0, 1000) },
      issueId: issue._id,
      kind: `agent.${args.status}`,
      projectId: issue.projectId,
      runId: run._id,
    });
  },
});
