import { randomUUID } from "node:crypto";
import { definePlugin, runWorker, type PluginApiRequestInput } from "@paperclipai/plugin-sdk";

type SmokeInput = {
  companyId: string;
  issueId: string;
  assigneeAgentId?: string | null;
  actorAgentId?: string | null;
  actorUserId?: string | null;
  actorRunId?: string | null;
};

type SmokeSummary = {
  rootIssueId: string;
  childIssueId: string | null;
  blockerIssueId: string | null;
  billingCode: string;
  joinedRows: unknown[];
  subtreeIssueIds: string[];
  wakeupQueued: boolean;
};

let readSmokeSummary: ((companyId: string, issueId: string) => Promise<SmokeSummary | null>) | null = null;
let initializeSmoke: ((input: SmokeInput) => Promise<SmokeSummary>) | null = null;

function tableName(namespace: string) {
  return `${namespace}.smoke_runs`;
}

function stringField(value: unknown): string | null {
  return typeof value === "string" && value.trim().length > 0 ? value : null;
}

const plugin = definePlugin({
  async setup(ctx) {
    readSmokeSummary = async function readSummary(companyId: string, issueId: string): Promise<SmokeSummary | null> {
      const rows = await ctx.db.query<{
        root_issue_id: string;
        child_issue_id: string | null;
        blocker_issue_id: string | null;
        billing_code: string;
        issue_title: string;
        last_summary: unknown;
      }>(
        `SELECT s.root_issue_id, s.child_issue_id, s.blocker_issue_id, s.billing_code, i.title AS issue_title, s.last_summary
         FROM ${tableName(ctx.db.namespace)} s
         JOIN public.issues i ON i.id = s.root_issue_id
         WHERE s.root_issue_id = $1`,
        [issueId],
      );
      const row = rows[0];
      if (!row) return null;
      const orchestration = await ctx.issues.summaries.getOrchestration({
        issueId,
        companyId,
        includeSubtree: true,
        billingCode: row.billing_code,
      });
      return {
        rootIssueId: row.root_issue_id,
        childIssueId: row.child_issue_id,
        blockerIssueId: row.blocker_issue_id,
        billingCode: row.billing_code,
        joinedRows: rows,
        subtreeIssueIds: orchestration.subtreeIssueIds,
        wakeupQueued: Boolean((row.last_summary as { wakeupQueued?: unknown } | null)?.wakeupQueued),
      };
    };

    initializeSmoke = async function runSmoke(input: SmokeInput): Promise<SmokeSummary> {
      const root = await ctx.issues.get(input.issueId, input.companyId);
      if (!root) throw new Error(`Issue not found: ${input.issueId}`);

      const billingCode = `plugin-smoke:${input.issueId}`;
      const actor = {
        actorAgentId: input.actorAgentId ?? null,
        actorUserId: input.actorUserId ?? null,
        actorRunId: input.actorRunId ?? null,
      };
      const blocker = await ctx.issues.create({
        companyId: input.companyId,
        parentId: input.issueId,
        inheritExecutionWorkspaceFromIssueId: input.issueId,
        title: "Orchestration smoke blocker",
        description: "Resolved blocker used to verify plugin relation writes without preventing the smoke wakeup.",
        status: "done",
        priority: "low",
        billingCode,
        originKind: `plugin:${ctx.manifest.id}:blocker`,
        originId: `${input.issueId}:blocker`,
        actor,
      });

      const child = await ctx.issues.create({
        companyId: input.companyId,
        parentId: input.issueId,
        inheritExecutionWorkspaceFromIssueId: input.issueId,
        title: "Orchestration smoke child",
        description: "Generated by the orchestration smoke plugin to verify issue, document, relation, wakeup, and summary APIs.",
        status: "todo",
        priority: "medium",
        assigneeAgentId: input.assigneeAgentId ?? root.assigneeAgentId ?? undefined,
        billingCode,
        originKind: `plugin:${ctx.manifest.id}:child`,
        originId: `${input.issueId}:child`,
        blockedByIssueIds: [blocker.id],
        actor,
      });

      await ctx.issues.relations.setBlockedBy(child.id, [blocker.id], input.companyId, actor);
      await ctx.issues.documents.upsert({
        issueId: child.id,
        companyId: input.companyId,
        key: "orchestration-smoke",
        title: "Orchestration Smoke",
        format: "markdown",
        body: [
          "# Orchestration Smoke",
          "",
          `- Root issue: ${input.issueId}`,
          `- Child issue: ${child.id}`,
          `- Billing code: ${billingCode}`,
        ].join("\n"),
        changeSummary: "Recorded orchestration smoke output",
      });

      const wakeup = await ctx.issues.requestWakeup(child.id, input.companyId, {
        reason: "plugin:orchestration_smoke",
        contextSource: "plugin-orchestration-smoke",
        idempotencyKey: `${input.issueId}:child`,
        ...actor,
      });
      const orchestration = await ctx.issues.summaries.getOrchestration({
        issueId: input.issueId,
        companyId: input.companyId,
        includeSubtree: true,
        billingCode,
      });
      const summarySnapshot = {
        childIssueId: child.id,
        blockerIssueId: blocker.id,
        wakeupQueued: wakeup.queued,
        subtreeIssueIds: orchestration.subtreeIssueIds,
      };

      await ctx.db.execute(
        `INSERT INTO ${tableName(ctx.db.namespace)} (id, root_issue_id, child_issue_id, blocker_issue_id, billing_code, last_summary)
         VALUES ($1, $2, $3, $4, $5, $6::jsonb)
         ON CONFLICT (id) DO UPDATE SET
           child_issue_id = EXCLUDED.child_issue_id,
           blocker_issue_id = EXCLUDED.blocker_issue_id,
           billing_code = EXCLUDED.billing_code,
           last_summary = EXCLUDED.last_summary,
           updated_at = now()`,
        [
          randomUUID(),
          input.issueId,
          child.id,
          blocker.id,
          billingCode,
          JSON.stringify(summarySnapshot),
        ],
      );

      return {
        rootIssueId: input.issueId,
        childIssueId: child.id,
        blockerIssueId: blocker.id,
        billingCode,
        joinedRows: await ctx.db.query(
          `SELECT s.id, s.billing_code, i.title AS root_title
           FROM ${tableName(ctx.db.namespace)} s
           JOIN public.issues i ON i.id = s.root_issue_id
           WHERE s.root_issue_id = $1`,
          [input.issueId],
        ),
        subtreeIssueIds: orchestration.subtreeIssueIds,
        wakeupQueued: wakeup.queued,
      };
    };

    ctx.data.register("surface-status", async (params) => {
      const companyId = stringField(params.companyId);
      const issueId = stringField(params.issueId);
      return {
        status: "ok",
        checkedAt: new Date().toISOString(),
        databaseNamespace: ctx.db.namespace,
        routeKeys: (ctx.manifest.apiRoutes ?? []).map((route) => route.routeKey),
        capabilities: ctx.manifest.capabilities,
        summary: companyId && issueId ? await readSmokeSummary?.(companyId, issueId) ?? null : null,
      };
    });

    ctx.actions.register("initialize-smoke", async (params) => {
      const companyId = stringField(params.companyId);
      const issueId = stringField(params.issueId);
      if (!companyId || !issueId) throw new Error("companyId and issueId are required");
      if (!initializeSmoke) throw new Error("Smoke initializer is not ready");
      return initializeSmoke({
        companyId,
        issueId,
        assigneeAgentId: stringField(params.assigneeAgentId),
        actorAgentId: stringField(params.actorAgentId),
        actorUserId: stringField(params.actorUserId),
        actorRunId: stringField(params.actorRunId),
      });
    });
  },

  async onApiRequest(input: PluginApiRequestInput) {
    if (input.routeKey === "summary") {
      const issueId = input.params.issueId;
      return {
        body: await readSmokeSummary?.(input.companyId, issueId) ?? null,
      };
    }

    if (input.routeKey === "initialize") {
      if (!initializeSmoke) throw new Error("Smoke initializer is not ready");
      const body = input.body as Record<string, unknown> | null;
      return {
        status: 201,
        body: await initializeSmoke({
          companyId: input.companyId,
          issueId: input.params.issueId,
          assigneeAgentId: stringField(body?.assigneeAgentId),
          actorAgentId: input.actor.agentId ?? null,
          actorUserId: input.actor.userId ?? null,
          actorRunId: input.actor.runId ?? null,
        }),
      };
    }

    return {
      status: 404,
      body: { error: `Unknown orchestration smoke route: ${input.routeKey}` },
    };
  },

  async onHealth() {
    return {
      status: "ok",
      message: "Orchestration smoke plugin worker is running",
      details: {
        surfaces: ["database", "scoped-api-route", "issue-panel", "orchestration-apis"],
      },
    };
  }
});

export default plugin;
runWorker(plugin, import.meta.url);
