/**
 * Credential Injector
 *
 * 執行順序：
 * 1. 檢查是否有對應的 auth recipe（auth_recipe:{componentId} in RECIPES KV）
 *    → 有：走 auth recipe 路徑（支援 static_key, service_account）
 *    → 無：走舊有 flat injection 路徑（向後相容）
 *
 * Auth Recipe 路徑：
 *   - static_key：展開 inject.header/query/body 的 {{secret.KEY}} 模板
 *   - service_account：JWT signing → token exchange → 展開 {{runtime.access_token}}
 *   - 注入結果以 _auth_headers / _auth_query / _auth_body 攜帶，不污染業務欄位
 *
 * 舊有路徑（向後相容）：
 *   - 從 RECIPES KV 讀取 credentials_required（動態 recipe）
 *   - 或從 BUILTIN_CREDENTIALS_MAP（內建清單）
 *   - 解密後以 inject_as 欄位名稱直接注入 context
 */

import type { Bindings } from '../types';
import { resolveRecipe, resolveAuthRecipe } from '../routes/recipes';
import type { AuthRecipeDefinition } from '../routes/recipes';

export interface CredentialRequirement {
  key: string;       // CREDENTIALS_KV 的 credential 名稱（如 gmail_token）
  inject_as: string; // 注入到 input 的欄位名稱（如 access_token）
}

/** 內建 API recipe 的 credentials_required（對應 component-loader 的 BUILTIN_API_RECIPES）*/
const BUILTIN_CREDENTIALS_MAP: Record<string, CredentialRequirement[]> = {
  gmail:         [{ key: 'gmail_token',          inject_as: 'access_token' }],
  google_sheets: [{ key: 'google_oauth',          inject_as: 'access_token' }],
  telegram:      [{ key: 'telegram_bot_token',    inject_as: 'bot_token'    }],
  line_notify:   [{ key: 'line_token',            inject_as: 'token'        }],
};

// ── AES-GCM 解密 ──────────────────────────────────────────────────────────────

async function decryptCredential(encryptedJson: string, encryptionKey: string): Promise<string> {
  const { encrypted, iv } = JSON.parse(encryptedJson) as { encrypted: string; iv: string };

  const keyBytes = hexToUint8Array(encryptionKey);
  const cryptoKey = await crypto.subtle.importKey(
    'raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt'],
  );

  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: base64ToUint8Array(iv) },
    cryptoKey,
    base64ToUint8Array(encrypted),
  );

  return new TextDecoder().decode(decrypted);
}

function hexToUint8Array(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
  return bytes;
}

function base64ToUint8Array(b64: string): Uint8Array {
  const binary = atob(b64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
  return bytes;
}

// ── 解密所有 required_secrets → { key: decryptedValue } ──────────────────────

async function decryptSecrets(
  recipe: AuthRecipeDefinition,
  apiKey: string,
  env: Bindings,
): Promise<Record<string, string>> {
  const result: Record<string, string> = {};

  for (const req of recipe.required_secrets) {
    if (req.optional) continue;

    const kvKey = `${apiKey}:cred:${req.key}`;
    const record = await env.CREDENTIALS_KV.get(kvKey);

    if (!record) {
      throw new Error(
        `缺少 credential：${req.key}（${req.label}）\n` +
        `修復步驟：\n` +
        `  1. 在 credentials.yaml 加入 ${req.key}: "your-value"\n` +
        `  2. 執行：acr creds push`,
      );
    }

    result[req.key] = await decryptCredential(record, env.ENCRYPTION_KEY);
  }

  return result;
}

// ── Template 展開：{{secret.KEY}} 和 {{runtime.KEY}} ─────────────────────────

function interpolateTemplate(
  template: string,
  secrets: Record<string, string>,
  runtime: Record<string, string>,
): string {
  return template.replace(/\{\{(secret|runtime)\.(\w+)\}\}/g, (_, ns, key) => {
    if (ns === 'secret') return secrets[key] ?? '';
    if (ns === 'runtime') return runtime[key] ?? '';
    return '';
  });
}

function interpolateRecord(
  record: Record<string, string>,
  secrets: Record<string, string>,
  runtime: Record<string, string>,
): Record<string, string> {
  const result: Record<string, string> = {};
  for (const [k, v] of Object.entries(record)) {
    result[k] = interpolateTemplate(v, secrets, runtime);
  }
  return result;
}

// ── Auth Recipe 注入（新路徑）────────────────────────────────────────────────

async function injectFromAuthRecipe(
  recipe: AuthRecipeDefinition,
  input: Record<string, unknown>,
  env: Bindings,
  apiKey: string,
): Promise<Record<string, unknown>> {
  // 解密所有 required_secrets
  const secrets = await decryptSecrets(recipe, apiKey, env);

  // runtime token：service_account 路徑已改走 auth-dispatcher → auth_service_account WASM;
  // 這條 TS fallback 只處理 static_key (runtime 為空即可),service_account 永遠不會走到這裡
  const runtime: Record<string, string> = {};

  if (recipe.primitive === 'service_account') {
    throw new Error(
      `service_account primitive 應由 auth-dispatcher → auth_service_account WASM 處理,` +
      `不應進到 credential-injector TS fallback (service=${recipe.service})`,
    );
  }

  // 展開 inject 模板
  const authHeaders = recipe.inject.header
    ? interpolateRecord(recipe.inject.header, secrets, runtime)
    : {};
  const authQuery = recipe.inject.query
    ? interpolateRecord(recipe.inject.query, secrets, runtime)
    : {};
  const authBody = recipe.inject.body
    ? interpolateRecord(recipe.inject.body, secrets, runtime)
    : {};

  return {
    ...input,
    _auth_headers: authHeaders,
    _auth_query: authQuery,
    _auth_body: authBody,
  };
}

// ── 舊有路徑：flat injection（向後相容）──────────────────────────────────────

async function loadCredentialsRequired(
  componentId: string,
  env: Bindings,
): Promise<CredentialRequirement[]> {
  const recipe = await resolveRecipe(componentId, env.RECIPES);
  if (recipe?.credentials_required?.length) {
    return recipe.credentials_required;
  }
  return BUILTIN_CREDENTIALS_MAP[componentId] ?? [];
}

// ── 主入口 ────────────────────────────────────────────────────────────────────

/**
 * 執行 credential 注入。
 *
 * @param componentId - 零件 canonical_id 或 hash
 * @param input       - 節點的 merged context
 * @param env         - Cloudflare Worker Bindings
 * @param apiKey      - 用戶的 API Key（ak_前綴），作為 KV namespace
 */
export async function injectCredentials(
  componentId: string,
  input: Record<string, unknown>,
  env: Bindings,
  apiKey?: string,
): Promise<Record<string, unknown>> {
  // 沒有 api_key → local 模式，略過
  if (!apiKey) return input;

  // ── 新路徑：auth recipe ──
  const authRecipe = await resolveAuthRecipe(componentId, env.RECIPES);
  if (authRecipe) {
    return injectFromAuthRecipe(authRecipe, input, env, apiKey);
  }

  // ── 舊路徑：flat injection（向後相容）──
  const required = await loadCredentialsRequired(componentId, env);
  if (required.length === 0) return input;

  const enriched = { ...input };

  for (const cred of required) {
    const kvKey = `${apiKey}:cred:${cred.key}`;
    const record = await env.CREDENTIALS_KV.get(kvKey);

    if (!record) {
      throw new Error(
        `缺少 credential：${cred.key}\n` +
        `修復步驟：\n` +
        `  1. 在 credentials.yaml 中加入 ${cred.key}: "your-token"\n` +
        `  2. 執行：acr creds push`,
      );
    }

    try {
      const decrypted = await decryptCredential(record, env.ENCRYPTION_KEY);
      enriched[cred.inject_as] = decrypted;
    } catch (e) {
      throw new Error(
        `credential "${cred.key}" 解密失敗：${e instanceof Error ? e.message : String(e)}\n` +
        `修復步驟：重新執行 acr creds push。`,
      );
    }
  }

  return enriched;
}
