// SPEC: PJ2026-01060703 CI/CD branch follower TaskRun drill-down.
// Responsibility: follower-scoped read-only TaskRun/Pod/container/log-tail visibility.
import type { CommandResult } from "./command";
import type { BranchFollowerRegistry, FollowerSpec, ParsedOptions } from "./cicd-types";
import { nativeCicdScriptLoadShell } from "./cicd-native-bundle";
import { redactText, shQuote } from "./platform-infra-ops-library";

type KubeScriptRunner = (registry: BranchFollowerRegistry, options: ParsedOptions, script: string, input: string, timeoutMs: number) => CommandResult;

export async function runBranchFollowerTaskRunDrillDown(
  registry: BranchFollowerRegistry,
  follower: FollowerSpec,
  options: ParsedOptions,
  runKubeScript: KubeScriptRunner,
): Promise<Record<string, unknown>> {
  if (follower.nativeStatus.tekton === null) throw new Error(`follower ${follower.id} has no Tekton native status config`);
  const taskRun = options.taskRunName;
  if (taskRun === null) throw new Error("taskrun drill-down requires --taskrun <taskrun-name|pipeline-task>");
  if (follower.drillDown === null) {
    return {
      ok: false,
      action: "taskrun",
      follower: follower.id,
      adapter: follower.adapter,
      degradedReason: "drilldown-policy-missing",
      message: `follower ${follower.id} registry is missing drillDown policy; inspect the owning YAML before TaskRun drill-down`,
      query: {
        taskRun,
        pipelineRun: options.pipelineRunName,
        tektonNamespace: follower.nativeStatus.tekton.namespace,
        pipelineRunPrefix: follower.nativeStatus.tekton.pipelineRunPrefix,
      },
      policy: null,
      result: null,
      statusAuthority: options.inCluster ? "kubernetes-api-serviceaccount" : "target-node-kubectl-raw",
      parsedDownstreamCliOutput: false,
      next: {
        status: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id}`,
        fixManualRelease: "docs/reference/platform-infra.md#gitea-与-pipelines-as-code-边界",
      },
    };
  }
  const policy = {
    taskRunTimeoutSeconds: options.timeoutSeconds ?? follower.drillDown.taskRunTimeoutSeconds,
    logsTailLines: options.logsTailLines ?? follower.drillDown.logsTailLines,
    maxLogBytes: options.maxLogBytes ?? follower.drillDown.maxLogBytes,
    maxMessageBytes: follower.drillDown.maxMessageBytes,
    maxContainers: follower.drillDown.maxContainers,
  };
  const script = [
    "set -eu",
    "tmpdir=$(mktemp -d)",
    "cleanup() { rm -rf \"$tmpdir\"; }",
    "trap cleanup EXIT INT TERM",
    nativeCicdScriptLoadShell(["taskrun-drilldown.mjs"]),
    `TEKTON_NAMESPACE=${shQuote(follower.nativeStatus.tekton.namespace)}`,
    `TASKRUN_QUERY=${shQuote(taskRun)}`,
    `PIPELINE_RUN_NAME=${shQuote(options.pipelineRunName ?? "")}`,
    `PIPELINE_RUN_PREFIX=${shQuote(follower.nativeStatus.tekton.pipelineRunPrefix)}`,
    `LOGS_TAIL_LINES=${policy.logsTailLines}`,
    `MAX_LOG_BYTES=${policy.maxLogBytes}`,
    `MAX_MESSAGE_BYTES=${policy.maxMessageBytes}`,
    `MAX_CONTAINERS=${policy.maxContainers}`,
    "export TEKTON_NAMESPACE TASKRUN_QUERY PIPELINE_RUN_NAME PIPELINE_RUN_PREFIX LOGS_TAIL_LINES MAX_LOG_BYTES MAX_MESSAGE_BYTES MAX_CONTAINERS",
    "node \"$tmpdir/taskrun-drilldown.mjs\"",
  ].join("\n");
  const startedAt = Date.now();
  const result = runKubeScript(registry, options, script, "", policy.taskRunTimeoutSeconds * 1000);
  const parsedResult = result.exitCode === 0 ? parseJsonObject(result.stdout) : { value: null, error: "target-command-failed" };
  const parsed = parsedResult.value;
  const includeTails = result.exitCode !== 0 || parsed === null || parsed.ok === false;
  return {
    ok: result.exitCode === 0 && parsed !== null && parsed.ok !== false,
    action: "taskrun",
    follower: follower.id,
    adapter: follower.adapter,
    statusAuthority: options.inCluster ? "kubernetes-api-serviceaccount" : "target-node-kubectl-raw",
    parsedDownstreamCliOutput: false,
    query: {
      taskRun,
      pipelineRun: options.pipelineRunName,
      tektonNamespace: follower.nativeStatus.tekton.namespace,
      pipelineRunPrefix: follower.nativeStatus.tekton.pipelineRunPrefix,
    },
    policy,
    result: parsed,
    command: {
      identity: {
        route: options.inCluster ? "in-cluster" : registry.controller.kubeRoute,
        script: "scripts/native/cicd/taskrun-drilldown.mjs",
        namespace: follower.nativeStatus.tekton.namespace,
        taskRun,
        pipelineRun: options.pipelineRunName,
      },
      exitCode: result.exitCode,
      timedOut: result.timedOut,
      elapsedMs: Date.now() - startedAt,
      parseError: parsedResult.error,
      stdoutTail: includeTails ? redactText(tailText(result.stdout, 1600)) : "",
      stderrTail: includeTails ? redactText(tailText(result.stderr, 1200)) : "",
    },
    next: {
      status: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id}`,
      taskRunJson: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id} --taskrun ${taskRun} --logs-tail ${policy.logsTailLines} --json`,
    },
  };
}

function parseJsonObject(text: string): { value: Record<string, unknown> | null; error: string | null } {
  const trimmed = text.trim();
  if (trimmed.length === 0) return { value: null, error: "empty-stdout" };
  try {
    const parsed = JSON.parse(trimmed) as unknown;
    return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
      ? { value: parsed as Record<string, unknown>, error: null }
      : { value: null, error: "stdout-json-not-object" };
  } catch (error) {
    return { value: null, error: `stdout-json-parse-failed: ${error instanceof Error ? error.message : String(error)}` };
  }
}

function tailText(text: string, maxChars: number): string {
  return text.length <= maxChars ? text : text.slice(text.length - maxChars);
}
