import { execa } from "execa";
import type {
  CheckpointResult,
  L2Plan,
  PipelineContext,
  SpecReviewSession,
} from "../types.js";
import { getConfig } from "../config.js";

/**
 * Phase 13: side-effect that runs after `l2_review` returns `passed: true`
 * with an "approve" verdict. Creates a tracking issue at
 * `juspay/grace` (configurable) via the local `gh` CLI so approved tech
 * specs land in a central tracker.
 *
 * Contract — see the decision tree in the Phase 13 plan:
 *   - Only fires when result.passed && decision === "approve".
 *   - Gated by `config.checkpoints.l2_review.createGraceIssue`.
 *   - Idempotent on `ctx.artifacts.l2GraceIssueUrl` (already set → skip).
 *   - Guards against missing L2 / specContent (warn + skip, don't fail).
 *   - `gh issue create` failure: warn + continue (l2_review still passes).
 *     External-system failures don't gate the pipeline.
 *
 * Body is piped via `--body-file -` to avoid argv length limits for large
 * specContent payloads.
 */

const MAX_BODY_BYTES = 60_000; // GitHub issue body limit is ~65 KB; leave headroom.
const GH_TIMEOUT_MS = 30_000;

function buildIssueTitle(connector: string, paymentMethod: string): string {
  return `[Tech Spec] ${connector} — ${paymentMethod}`;
}

function buildIssueBody(
  connector: string,
  paymentMethod: string,
  runId: string,
  l2: L2Plan
): string {
  const constraints = (l2.technicalConstraints ?? [])
    .map((c) => `- ${c}`)
    .join("\n");

  const body = [
    `# ${connector} — ${paymentMethod}`,
    "",
    `**Complexity:** ${l2.estimatedComplexity ?? "unknown"}`,
    `**Generated by:** Grace pipeline run \`${runId}\``,
    "",
    "## Summary",
    "",
    l2.summary?.trim() || "_(no summary)_",
    "",
    "## Scope",
    "",
    l2.scope?.trim() || "_(no scope)_",
    "",
    "## Out of Scope",
    "",
    l2.outOfScope?.trim() || "_(no explicit non-goals)_",
    "",
    "## Technical Constraints",
    "",
    constraints || "_(none)_",
    "",
    "---",
    "",
    "## Full Tech Spec",
    "",
    l2.specContent?.trim() || "_(specContent missing — see Grace run artifacts)_",
  ].join("\n");

  if (body.length <= MAX_BODY_BYTES) return body;
  // Truncate if oversized — keep the header (small) intact and clip the
  // specContent tail with a clear marker so the issue is still useful.
  const truncatedAt = MAX_BODY_BYTES - 200;
  return (
    body.slice(0, truncatedAt) +
    `\n\n…\n\n_(truncated at ${truncatedAt} bytes; see Grace run \`${runId}\`'s ` +
    `ctx.artifacts.l2.specContent for the full spec)_`
  );
}

/**
 * Wraps the l2_review checkpoint result. Called immediately after
 * `runHumanReview` returns, before the engine sees the result. If the side
 * effect succeeds, returns a new CheckpointResult with `l2GraceIssueUrl`
 * merged into `artifacts` so the engine persists it via the usual
 * `Object.assign(ctx.artifacts, result.artifacts)` step in engine.ts.
 */
/**
 * Sibling of `maybeCreateGraceIssue` for the new-connector pipeline. That
 * pipeline skips L2/L3 review (the techspec is captured up-front by the
 * wizard), so there's no `l2Review` decision to gate on and no
 * `ctx.artifacts.l2` to read. Instead we draw the spec straight from
 * `task.techSpecMarkdown` and fire once at task-checkpoint pass.
 *
 * Same gh CLI / config gate / idempotency pattern as the l2_review case —
 * just a different data source and idempotency key.
 */
export async function maybeCreateNewConnectorGraceIssue(
  ctx: PipelineContext,
  result: CheckpointResult
): Promise<CheckpointResult> {
  if (!result.passed) return result;

  const task = ctx.task;
  if (task.workflowType !== "new-connector") return result;

  // Share the same config gate as l2_review — operators expect ONE knob.
  const cfg = getConfig().checkpoints.l2_review;
  if (cfg.createGraceIssue === false) {
    ctx.log(
      "[task] new-connector grace issue creation skipped (createGraceIssue=false in config)",
      "info"
    );
    return result;
  }

  if (ctx.artifacts.newConnectorGraceIssueUrl) {
    ctx.log(
      `[task] new-connector grace issue already created at ${ctx.artifacts.newConnectorGraceIssueUrl} — idempotency skip`,
      "info"
    );
    return result;
  }

  const spec = task.techSpecMarkdown?.trim();
  if (!spec) {
    ctx.log(
      "[task] cannot create new-connector grace issue: task.techSpecMarkdown is empty. " +
        "(Wizard auto-discovery may have failed; user can file the issue manually.)",
      "warn"
    );
    return result;
  }

  const connector = task.targetConnectors?.[0] || "Unknown";
  const flowsList = (task.flows && task.flows.length > 0
    ? task.flows
    : ["Authorize", "PSync", "Capture", "Refund", "RSync", "Void"]).join(", ");
  const title = `[New Connector] ${connector} — ${flowsList}`;

  const headerLines = [
    `# ${connector} — New Connector Integration`,
    "",
    `**Workflow:** new-connector`,
    `**Flows:** ${flowsList}`,
    task.authScheme ? `**Auth scheme:** ${task.authScheme}` : null,
    task.currencyUnit ? `**Currency unit:** ${task.currencyUnit}` : null,
    task.baseUrl ? `**Base URL:** ${task.baseUrl}` : null,
    task.sandboxUrl ? `**Sandbox URL:** ${task.sandboxUrl}` : null,
    `**Generated by:** Grace pipeline run \`${ctx.runId}\``,
  ].filter((l): l is string => l !== null);

  const docsBlock =
    task.discoveredConnectorUrls && task.discoveredConnectorUrls.length > 0
      ? [
          "",
          "## Discovered Docs",
          "",
          ...task.discoveredConnectorUrls.map((u) => `- ${u}`),
        ]
      : [];

  const acceptanceBlock =
    task.acceptanceCriteria && task.acceptanceCriteria.length > 0
      ? [
          "",
          "## Acceptance Criteria",
          "",
          ...task.acceptanceCriteria.map((c) => `- [ ] ${c}`),
        ]
      : [];

  const bodyDraft = [
    ...headerLines,
    ...docsBlock,
    ...acceptanceBlock,
    "",
    "---",
    "",
    "## Tech Spec",
    "",
    spec,
  ].join("\n");

  const body =
    bodyDraft.length <= MAX_BODY_BYTES
      ? bodyDraft
      : bodyDraft.slice(0, MAX_BODY_BYTES - 200) +
        `\n\n…\n\n_(truncated at ${MAX_BODY_BYTES - 200} bytes; see Grace run \`${ctx.runId}\`'s task.techSpecMarkdown for the full spec)_`;

  const repo = cfg.graceIssueRepo;
  const labels = cfg.graceIssueLabels ?? [];
  const args = ["issue", "create", "--repo", repo, "--title", title, "--body-file", "-"];
  for (const label of labels) args.push("--label", label);

  ctx.log(
    `[task] creating new-connector grace issue at ${repo}: "${title}" (body=${body.length}ch, labels=[${labels.join(", ")}])`,
    "info"
  );

  try {
    const res = await execa("gh", args, {
      input: body,
      reject: false,
      all: true,
      timeout: GH_TIMEOUT_MS,
    });

    if (res.exitCode === 0) {
      const url = (res.stdout || "")
        .trim()
        .split(/\r?\n/)
        .filter((l) => l.startsWith("http"))
        .pop();
      if (!url) {
        ctx.log(
          `[task] gh issue create exited 0 but no URL parsed from stdout (got: ${(res.stdout ?? "").slice(0, 200)}). Continuing.`,
          "warn"
        );
        return result;
      }
      ctx.log(`[task] ✓ new-connector grace issue created: ${url}`, "success");
      return {
        ...result,
        artifacts: {
          ...(result.artifacts ?? {}),
          newConnectorGraceIssueUrl: url,
        },
      };
    }

    const stderrTail = (res.all ?? "").slice(-500).trim();
    ctx.log(
      `[task] gh issue create failed (exit ${res.exitCode}): ${stderrTail}. ` +
        `Run \`gh auth status\` to check credentials. Continuing without grace issue.`,
      "warn"
    );
    return result;
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    ctx.log(
      `[task] gh issue create threw: ${msg}. Continuing without grace issue.`,
      "warn"
    );
    return result;
  }
}

export async function maybeCreateGraceIssue(
  ctx: PipelineContext,
  result: CheckpointResult
): Promise<CheckpointResult> {
  // Bail-out conditions, in the order from the decision tree.
  if (!result.passed) return result;

  const reviewSession =
    (result.artifacts?.l2Review as SpecReviewSession | undefined) ??
    ctx.artifacts.l2Review;
  if (reviewSession?.decision !== "approve") return result;

  const cfg = getConfig().checkpoints.l2_review;
  if (cfg.createGraceIssue === false) {
    ctx.log(
      "[l2_review] grace issue creation skipped (createGraceIssue=false in config)",
      "info"
    );
    return result;
  }

  if (ctx.artifacts.l2GraceIssueUrl) {
    ctx.log(
      `[l2_review] grace issue already created at ${ctx.artifacts.l2GraceIssueUrl} — idempotency skip`,
      "info"
    );
    return result;
  }

  const l2 = ctx.artifacts.l2;
  if (!l2 || !l2.specContent) {
    ctx.log(
      "[l2_review] cannot create grace issue: ctx.artifacts.l2 is missing or has no specContent. " +
        "Continuing without grace issue.",
      "warn"
    );
    return result;
  }

  const task = ctx.task;
  const connector =
    (task.targetConnectors && task.targetConnectors[0]) || "Unknown";
  const paymentMethod = task.paymentMethod || "Unknown";
  const title = buildIssueTitle(connector, paymentMethod);
  const body = buildIssueBody(connector, paymentMethod, ctx.runId, l2);

  const repo = cfg.graceIssueRepo;
  const labels = cfg.graceIssueLabels ?? [];

  const args = ["issue", "create", "--repo", repo, "--title", title, "--body-file", "-"];
  for (const label of labels) args.push("--label", label);

  ctx.log(
    `[l2_review] creating grace issue at ${repo}: "${title}" (body=${body.length}ch, labels=[${labels.join(", ")}])`,
    "info"
  );

  try {
    const res = await execa("gh", args, {
      input: body,
      reject: false,
      all: true,
      timeout: GH_TIMEOUT_MS,
    });

    if (res.exitCode === 0) {
      // `gh issue create` prints the issue URL on the final line of stdout.
      const url = (res.stdout || "")
        .trim()
        .split(/\r?\n/)
        .filter((l) => l.startsWith("http"))
        .pop();
      if (!url) {
        ctx.log(
          `[l2_review] gh issue create exited 0 but no URL parsed from stdout (got: ${(res.stdout ?? "").slice(0, 200)}). Continuing.`,
          "warn"
        );
        return result;
      }
      ctx.log(`[l2_review] ✓ grace issue created: ${url}`, "success");
      return {
        ...result,
        artifacts: {
          ...(result.artifacts ?? {}),
          l2GraceIssueUrl: url,
        },
      };
    }

    const stderrTail = (res.all ?? "").slice(-500).trim();
    ctx.log(
      `[l2_review] gh issue create failed (exit ${res.exitCode}): ${stderrTail}. ` +
        `Run \`gh auth status\` to check credentials. Continuing without grace issue — file one manually if needed.`,
      "warn"
    );
    return result;
  } catch (err) {
    // Most likely cause: gh binary missing or timeout. Both are external
    // failures — log and let the pipeline continue.
    const msg = err instanceof Error ? err.message : String(err);
    ctx.log(
      `[l2_review] gh issue create threw: ${msg}. Continuing without grace issue.`,
      "warn"
    );
    return result;
  }
}
