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

import type { Id } from "./_generated/dataModel";
import { mutation, query } from "./_generated/server";
import type { MutationCtx } from "./_generated/server";
import { requireOwnerId } from "./authz";

const requireProjectOwner = async (
  ctx: MutationCtx,
  projectId: Id<"projects">,
  ownerId: string
) => {
  const project = await ctx.db.get("projects", projectId);
  if (!project || project.ownerId !== ownerId) {
    throw new ConvexError("Project not found");
  }
  return project;
};

export const create = mutation({
  args: {
    body: v.string(),
    projectId: v.id("projects"),
    title: v.string(),
  },
  handler: async (ctx, args) => {
    const ownerId = await requireOwnerId(ctx);
    await requireProjectOwner(ctx, args.projectId, ownerId);
    const title = args.title.trim();
    const body = args.body.trim();
    if (title.length < 3 || title.length > 160) {
      throw new ConvexError("Issue title must be between 3 and 160 characters");
    }
    if (body.length < 10 || body.length > 10_000) {
      throw new ConvexError(
        "Issue description must be between 10 and 10000 characters"
      );
    }
    const latest = await ctx.db
      .query("projectIssues")
      .withIndex("by_project_and_number", (q) =>
        q.eq("projectId", args.projectId)
      )
      .order("desc")
      .first();
    const timestamp = Date.now();
    const number = (latest?.number ?? 0) + 1;
    const issueId = await ctx.db.insert("projectIssues", {
      body,
      createdAt: timestamp,
      number,
      projectId: args.projectId,
      status: "open",
      title,
      updatedAt: timestamp,
    });
    const workArtifact = await ctx.db
      .query("projectArtifacts")
      .withIndex("by_project_and_path", (q) =>
        q.eq("projectId", args.projectId).eq("path", "work.md")
      )
      .unique();
    if (workArtifact) {
      await ctx.db.patch("projectArtifacts", workArtifact._id, {
        content: `${workArtifact.content}\n## Issue ${number}: ${title}\n\nStatus: open\n\n${body}\n`,
        revision: workArtifact.revision + 1,
        updatedAt: timestamp,
      });
    }
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { number, title },
      issueId,
      kind: "issue.created",
      projectId: args.projectId,
    });
    return issueId;
  },
});

export const begin = mutation({
  args: { issueId: v.id("projectIssues") },
  handler: async (ctx, args) => {
    const ownerId = await requireOwnerId(ctx);
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    await requireProjectOwner(ctx, issue.projectId, ownerId);
    if (issue.status === "queued" || issue.status === "working") {
      return issue.status;
    }
    const timestamp = Date.now();
    await ctx.db.patch("projectIssues", issue._id, {
      status: "queued",
      updatedAt: timestamp,
    });
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { number: issue.number },
      issueId: issue._id,
      kind: "issue.queued",
      projectId: issue.projectId,
    });
    return "queued" as const;
  },
});

export const markDispatchFailed = mutation({
  args: { error: v.string(), issueId: v.id("projectIssues") },
  handler: async (ctx, args) => {
    const ownerId = await requireOwnerId(ctx);
    const issue = await ctx.db.get("projectIssues", args.issueId);
    if (!issue) {
      throw new ConvexError("Issue not found");
    }
    await requireProjectOwner(ctx, issue.projectId, ownerId);
    const timestamp = Date.now();
    await ctx.db.patch("projectIssues", issue._id, {
      status: "failed",
      updatedAt: timestamp,
    });
    await ctx.db.insert("projectSignals", {
      createdAt: timestamp,
      data: { error: args.error.slice(0, 1000), phase: "dispatch" },
      issueId: issue._id,
      kind: "agent.failed",
      projectId: issue.projectId,
    });
  },
});

export const list = query({
  args: { projectId: v.id("projects") },
  handler: async (ctx, args) => {
    const ownerId = await requireOwnerId(ctx);
    const project = await ctx.db.get("projects", args.projectId);
    if (!project || project.ownerId !== ownerId) {
      return [];
    }
    return await ctx.db
      .query("projectIssues")
      .withIndex("by_project_and_number", (q) =>
        q.eq("projectId", args.projectId)
      )
      .order("desc")
      .take(100);
  },
});
