/**
 * acr run <workflow_name> [--input key=value...]
 *
 * 玩法一（Standard / Local）：
 *   在本機找 <workflow_name>.yaml，解析 triplets + config，
 *   直接 POST /cypher/execute 給 cypher.arcrun.dev 執行。
 *   YAML 不存在 KV，每次執行都帶著走。
 *
 * 玩法二（Self-hosted，workflow 已 push 到 KV）：
 *   POST /webhooks/<workflow_name>，executor 從 WEBHOOKS KV 讀取定義執行。
 */
import chalk from 'chalk';
import ora from 'ora';
import { existsSync, readFileSync } from 'node:fs';
import { loadConfig, getCypherExecutorUrl } from '../lib/config.js';
import { loadWorkflowYaml, parseTriplets } from '../lib/yaml-parser.js';

interface RunOptions {
  input?: string[];
}

export async function cmdRun(workflowName: string, options: RunOptions): Promise<void> {
  const config = loadConfig();
  const executorUrl = getCypherExecutorUrl(config);

  // 解析 --input key=value 為 JSON object
  const inputContext: Record<string, string> = {};
  for (const pair of (options.input ?? [])) {
    const idx = pair.indexOf('=');
    if (idx < 0) {
      console.error(chalk.red(`--input 格式錯誤：${pair}（應為 key=value）`));
      process.exit(1);
    }
    inputContext[pair.slice(0, idx)] = pair.slice(idx + 1);
  }

  const headers: Record<string, string> = { 'Content-Type': 'application/json' };
  if (config.api_key) headers['X-Arcrun-API-Key'] = config.api_key;

  // ── 玩法一：本機有 YAML → 直接帶著打 /cypher/execute（不需先 push）──────────────
  // 2026-06-09 修：原本只有 standard/local 走這條，self-hosted 一律走玩法二（/webhooks/<name>，
  // 需先 push 到 KV）。導致 self-hosted 用戶（如壓測 Haiku）有本機 YAML 卻 acr run 直接打
  // /webhooks/<name> → 沒 push = 404 純文字 → res.json() 爆「Unexpected non-whitespace...」假錯誤。
  // 正解：只要本機找得到 YAML 就走玩法一直接執行（三模式一致）；找不到才退玩法二（按名字打已 push 的）。
  const localYamlPath = findWorkflowYaml(workflowName);
  if (localYamlPath) {
    const yamlPath = localYamlPath;

    let workflow;
    try {
      workflow = loadWorkflowYaml(yamlPath);
    } catch (e) {
      console.error(chalk.red(`YAML 解析失敗：${e instanceof Error ? e.message : e}`));
      process.exit(1);
    }

    const triplets = parseTriplets(workflow.flow);

    const spinner = ora(`執行 workflow "${workflow.name}"`).start();

    try {
      const res = await fetch(`${executorUrl}/cypher/execute`, {
        method: 'POST',
        headers,
        body: JSON.stringify({
          triplets: workflow.flow,
          config: workflow.config ?? {},   // node_name → {component, ...params}
          context: inputContext,           // --input key=value 傳入的動態參數
          graph_id: workflow.name,
          graph_name: workflow.name,
        }),
      });

      // 非 2xx 先擋：直接 res.json() 對 404「Not Found」這種純文字會爆出誤導的
      // 「Unexpected non-whitespace character after JSON」。看 res.ok 給人話。
      if (!res.ok) {
        const body = await res.text();
        spinner.fail(chalk.red(`執行失敗（HTTP ${res.status}）：${body.slice(0, 200)}`));
        process.exit(1);
      }

      const data = await res.json() as {
        success: boolean;
        data?: unknown;
        error?: string;
        trace?: Array<{ node: string; status: string; error?: string }>;
        duration_ms: number;
        failed_node?: string;
      };

      printResult(spinner, data, triplets.length);
    } catch (e) {
      spinner.fail(chalk.red(`網路錯誤：${e instanceof Error ? e.message : e}`));
      process.exit(1);
    }
    return;
  }

  // ── 玩法二：本機沒這個 YAML → 按名字打已 push 到 KV 的 workflow ───────────────
  // thin-shell-alignment P0（issue #11）：原打 /webhooks/${name} 是死端點（404）。
  // 真端點是 /webhooks/named/:name/trigger（webhooks-named.ts:279，X-Arcrun-API-Key header）。
  const spinner = ora(`執行 workflow "${workflowName}"`).start();
  try {
    const res = await fetch(`${executorUrl}/webhooks/named/${workflowName}/trigger`, {
      method: 'POST',
      headers,
      body: JSON.stringify(inputContext),
    });

    // 非 2xx 先擋（同玩法一）：404 純文字別硬 res.json()。404 多半是「還沒 push」。
    if (!res.ok) {
      if (res.status === 404) {
        spinner.fail(chalk.red(`找不到已部署的 workflow "${workflowName}"`));
        console.error(chalk.gray(`  本機也沒有 ${workflowName}.yaml。請確認：`));
        console.error(chalk.gray(`  ① 本機有 YAML → 在該檔所在目錄跑 acr run（會直接執行，不需先 push）`));
        console.error(chalk.gray(`  ② 要跑已部署的 → 先 acr push <file>.yaml 再 acr run <name>`));
      } else {
        const body = await res.text();
        spinner.fail(chalk.red(`執行失敗（HTTP ${res.status}）：${body.slice(0, 200)}`));
      }
      process.exit(1);
    }

    const data = await res.json() as {
      success: boolean;
      data?: unknown;
      error?: string;
      trace?: Array<{ node: string; status: string; error?: string }>;
      duration_ms: number;
      failed_node?: string;
    };

    printResult(spinner, data, 0);
  } catch (e) {
    spinner.fail(chalk.red(`網路錯誤：${e instanceof Error ? e.message : e}`));
    process.exit(1);
  }
}

// ── helpers ──────────────────────────────────────────────────────────────────

function findWorkflowYaml(name: string): string | null {
  // 容忍使用者直接給含副檔名的檔名（acr run foo.yaml）——剝掉再補，避免找成 foo.yaml.yaml。
  const base = name.replace(/\.(ya?ml)$/i, '');
  const candidates = [
    name,                     // 原樣（已含副檔名或本就是路徑）
    `${base}.yaml`,
    `${base}.yml`,
    `workflows/${base}.yaml`,
    `workflows/${base}.yml`,
  ];
  for (const p of candidates) {
    if (existsSync(p)) return p;
  }
  return null;
}

function printResult(
  spinner: ReturnType<typeof ora>,
  data: { success: boolean; data?: unknown; error?: string; trace?: Array<{ node: string; status: string; error?: string }>; duration_ms: number; failed_node?: string },
  _nodeCount: number,
): void {
  if (data.success) {
    spinner.succeed(chalk.green(`✓ 執行成功（${data.duration_ms}ms）`));
    if (data.data !== undefined && data.data !== null) {
      console.log('\n  結果：');
      console.log(JSON.stringify(data.data, null, 2).split('\n').map(l => `  ${l}`).join('\n'));
    }
  } else {
    spinner.fail(chalk.red(`✗ 執行失敗（${data.duration_ms}ms）`));
    if (data.failed_node) console.log(chalk.red(`\n  失敗節點：${data.failed_node}`));
    if (data.error) console.log(chalk.red(`  錯誤：${data.error}`));
    if (data.trace) {
      console.log('\n  執行追蹤：');
      for (const step of data.trace) {
        const icon = step.status === 'failed' ? chalk.red('✗') : chalk.green('✓');
        console.log(`    ${icon} ${step.node}${step.error ? ` — ${step.error}` : ''}`);
      }
    }
  }
}
