// auth_static_key — static key auth primitive
//
// 讀取 auth_recipe:{service} + 解密 required_secrets + 展開 {{secret.X}} 模板,
// 回傳 auth_headers / auth_query / auth_body。
//
// 所有外部 I/O 都透過 host function:
//   - u6u.kv_get          — 依 key 前綴路由到 RECIPES / CREDENTIALS_KV (host 做越權檢查)
//   - u6u.crypto_decrypt  — AES-GCM 解密 (encryption key 由 host 持有,不暴露給 WASM)
//
//go:build tinygo

package main

import (
	"encoding/base64"
	"encoding/json"
	"io"
	"os"
	"strings"
	"unsafe"
)

// ── host function 宣告 ───────────────────────────────────────────────────────

// kv_get(keyPtr, keyLen, outPtr, outLenPtr) → 0 成功 / 1 錯誤 / 2 找不到
//
//go:wasmimport u6u kv_get
func hostKvGet(
	keyPtr uintptr, keyLen uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

// crypto_decrypt(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) → 0 成功
// enc/iv 為 base64 字串(即 KV 中儲存的格式)
//
//go:wasmimport u6u crypto_decrypt
func hostCryptoDecrypt(
	encPtr uintptr, encLen uint32,
	ivPtr uintptr, ivLen uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

// ── 型別 ─────────────────────────────────────────────────────────────────────

type Input struct {
	Action  string          `json:"action"`
	APIKey  string          `json:"api_key"`
	Service string          `json:"service"`
	Request json.RawMessage `json:"request,omitempty"`
	// Names：resolve_credentials action 用——要解密的 credential 名稱清單
	// （用戶在 workflow node.data 寫 {{credential.NAME}} 時，graph-executor 收集後傳入）。
	Names []string `json:"names,omitempty"`
	// ResolvedSecrets：credential-store 遷移 T6/T7 方案 A（D19）——cypher-executor 已從
	// 自己的 per-script secrets（新家）用 secret_get(D1 ref) 取到的明文，key = credential name。
	// 有值就優先用，沒有才 fallback 舊路徑（kv_get {api_key}:cred:{name} + crypto_decrypt），
	// 這個 fallback 就是雙讀（T7）。default（此欄空/nil）行為完全等於遷移前的舊碼。
	ResolvedSecrets map[string]string `json:"resolved_secrets,omitempty"`
}

type SecretRequirement struct {
	Key      string `json:"key"`
	Label    string `json:"label"`
	Optional bool   `json:"optional,omitempty"`
}

type AuthInjectSpec struct {
	Header map[string]string `json:"header,omitempty"`
	Query  map[string]string `json:"query,omitempty"`
	Body   map[string]string `json:"body,omitempty"`
	// Path：要注入 endpoint URL path 的 secret（如 telegram /bot{token}/）。
	// key = 模板變數名（recipe endpoint 用 {{auth.K}} 引用），value = {{secret.X}} 模板。
	Path map[string]string `json:"path,omitempty"`
}

type AuthRecipe struct {
	Kind            string              `json:"kind"`
	Service         string              `json:"service"`
	Primitive       string              `json:"primitive"`
	RequiredSecrets []SecretRequirement `json:"required_secrets"`
	Inject          AuthInjectSpec      `json:"inject"`
}

type EncryptedRecord struct {
	Encrypted string `json:"encrypted"`
	IV        string `json:"iv"`
}

// ── main ─────────────────────────────────────────────────────────────────────

func main() {
	raw, err := io.ReadAll(os.Stdin)
	if err != nil {
		writeError("failed to read stdin: " + err.Error())
		return
	}

	var input Input
	if err := json.Unmarshal(raw, &input); err != nil {
		writeError("invalid input JSON: " + err.Error())
		return
	}

	if input.APIKey == "" {
		writeError("api_key 必填")
		return
	}

	// resolve_credentials：用戶面 {{credential.NAME}} 入口。不查 recipe、不要求 service，
	// 直接給 names 解密回明文。在 service 必填檢查之前分流（只有 authenticate 才需要 recipe）。
	if input.Action == "resolve_credentials" {
		handleResolveCredentials(input)
		return
	}

	if input.Service == "" {
		writeError("service 必填")
		return
	}
	if input.Action != "" && input.Action != "authenticate" {
		writeError("auth_static_key 僅支援 action=authenticate / resolve_credentials")
		return
	}

	// 1. 讀 auth recipe
	recipeJSON, status := kvGet("auth_recipe:" + input.Service)
	if status == 2 {
		writeError("找不到 auth recipe: " + input.Service)
		return
	}
	if status != 0 {
		writeError("kv_get 失敗(auth_recipe)")
		return
	}

	var recipe AuthRecipe
	if err := json.Unmarshal([]byte(recipeJSON), &recipe); err != nil {
		writeError("auth recipe JSON 解析失敗: " + err.Error())
		return
	}
	if recipe.Primitive != "static_key" {
		writeError("auth recipe " + input.Service + " 的 primitive 不是 static_key (是 " + recipe.Primitive + ")")
		return
	}

	// 2. 解密所有 non-optional required_secrets
	secrets := make(map[string]string)
	for _, req := range recipe.RequiredSecrets {
		if req.Optional {
			continue
		}

		// T6 方案 A：cypher 已從新家（per-script secrets）取到明文就直接用，不走 KV。
		// input.ResolvedSecrets 為 nil 時此 map 讀取回 ("", false)，行為與遷移前一致。
		if v, ok := input.ResolvedSecrets[req.Key]; ok {
			secrets[req.Key] = v
			continue
		}

		// T7 雙讀 fallback：新家沒給 → 走舊路徑 kv_get + crypto_decrypt。
		kvKey := input.APIKey + ":cred:" + req.Key
		encJSON, s := kvGet(kvKey)
		if s == 2 {
			writeError("缺少 credential: " + req.Key + " (" + req.Label + ")。修復: 編輯 credentials.yaml 後執行 acr creds push")
			return
		}
		if s != 0 {
			writeError("kv_get 失敗(credential " + req.Key + ")")
			return
		}

		var rec EncryptedRecord
		if err := json.Unmarshal([]byte(encJSON), &rec); err != nil {
			writeError("credential " + req.Key + " 格式錯誤: " + err.Error())
			return
		}

		plaintext, ok := cryptoDecrypt(rec.Encrypted, rec.IV)
		if !ok {
			writeError("credential " + req.Key + " 解密失敗")
			return
		}
		secrets[req.Key] = plaintext
	}

	// 3. 展開模板 (static_key 沒有 runtime,傳空 map)
	runtime := map[string]string{}
	authHeaders := interpolateRecord(recipe.Inject.Header, secrets, runtime)
	authQuery := interpolateRecord(recipe.Inject.Query, secrets, runtime)
	authBody := interpolateRecord(recipe.Inject.Body, secrets, runtime)
	authPath := interpolateRecord(recipe.Inject.Path, secrets, runtime)

	// 3.5 Basic Auth 自動編碼:若 header 值為 "Basic <x>:<y>" (冒號代表未編碼的 user:pass),
	//     將冒號分隔部分做 base64。這涵蓋 twilio / jira / mailgun 等 Basic Auth recipe。
	//     "Basic <already-base64>" (無冒號) 維持原樣,向後相容。
	//     header key 不分大小寫比對 "authorization"。
	for k, v := range authHeaders {
		if !strings.EqualFold(k, "Authorization") {
			continue
		}
		const prefix = "Basic "
		if !strings.HasPrefix(v, prefix) {
			continue
		}
		payload := v[len(prefix):]
		if !strings.Contains(payload, ":") {
			continue
		}
		authHeaders[k] = prefix + base64.StdEncoding.EncodeToString([]byte(payload))
	}

	// 4. 輸出
	out, _ := json.Marshal(map[string]interface{}{
		"success":      true,
		"auth_headers": authHeaders,
		"auth_query":   authQuery,
		"auth_body":    authBody,
		"auth_path":    authPath,
		"runtime":      runtime,
	})
	os.Stdout.Write(out)
}

// handleResolveCredentials 處理用戶面 {{credential.NAME}} 入口：
// 對每個 name 讀 {api_key}:cred:{name} + 解密，回傳明文 map。
// 不查 auth recipe（與 authenticate 分流）。缺任一 name → success:false + error 指明（不假綠）。
func handleResolveCredentials(input Input) {
	if len(input.Names) == 0 {
		writeError("resolve_credentials 需要 names（要解密的 credential 名稱清單）")
		return
	}

	credentials := make(map[string]string, len(input.Names))
	for _, name := range input.Names {
		if name == "" {
			continue
		}

		// T6 方案 A：cypher 已從新家取到明文就直接用（同 authenticate 分支）。
		if v, ok := input.ResolvedSecrets[name]; ok {
			credentials[name] = v
			continue
		}

		// T7 雙讀 fallback：新家沒給 → 舊路徑 kv_get + crypto_decrypt。
		kvKey := input.APIKey + ":cred:" + name
		encJSON, s := kvGet(kvKey)
		if s == 2 {
			writeError("缺少 credential: " + name + "。修復: 編輯 credentials.yaml 後執行 acr creds push")
			return
		}
		if s != 0 {
			writeError("kv_get 失敗(credential " + name + ")")
			return
		}

		var rec EncryptedRecord
		if err := json.Unmarshal([]byte(encJSON), &rec); err != nil {
			writeError("credential " + name + " 格式錯誤: " + err.Error())
			return
		}

		plaintext, ok := cryptoDecrypt(rec.Encrypted, rec.IV)
		if !ok {
			writeError("credential " + name + " 解密失敗")
			return
		}
		credentials[name] = plaintext
	}

	out, _ := json.Marshal(map[string]interface{}{
		"success":     true,
		"credentials": credentials,
	})
	os.Stdout.Write(out)
}

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

func writeError(msg string) {
	out, _ := json.Marshal(map[string]interface{}{
		"success":      false,
		"error":        msg,
		"auth_headers": map[string]string{},
		"auth_query":   map[string]string{},
		"auth_body":    map[string]string{},
	})
	os.Stdout.Write(out)
}

// kvGet 呼叫 host function,回傳 (value, status)。status: 0=成功 1=錯誤 2=找不到
func kvGet(key string) (string, uint32) {
	keyBytes := []byte(key)
	outBuf := make([]byte, 65536)
	var outLen uint32

	status := hostKvGet(
		uintptr(unsafe.Pointer(&keyBytes[0])), uint32(len(keyBytes)),
		uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
	)
	if status != 0 {
		return "", status
	}
	return string(outBuf[:outLen]), 0
}

// cryptoDecrypt 呼叫 host function 做 AES-GCM 解密
// enc/iv 均為 base64 字串;回傳 UTF-8 plaintext
func cryptoDecrypt(encB64, ivB64 string) (string, bool) {
	encBytes := []byte(encB64)
	ivBytes := []byte(ivB64)
	outBuf := make([]byte, 65536)
	var outLen uint32

	// 處理空字串的防呆(TinyGo 取 &[]byte{}[0] 會 panic)
	if len(encBytes) == 0 || len(ivBytes) == 0 {
		return "", false
	}

	status := hostCryptoDecrypt(
		uintptr(unsafe.Pointer(&encBytes[0])), uint32(len(encBytes)),
		uintptr(unsafe.Pointer(&ivBytes[0])), uint32(len(ivBytes)),
		uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
	)
	if status != 0 {
		return "", false
	}
	return string(outBuf[:outLen]), true
}

// interpolateTemplate 展開 {{secret.X}} 與 {{runtime.X}}。未知 key 展開為空字串(與 TS 版 parity)。
// 其他 namespace 的 {{...}} 原樣保留(static_key 不解析)。
func interpolateTemplate(template string, secrets, runtime map[string]string) string {
	var b strings.Builder
	b.Grow(len(template))
	i := 0
	for i < len(template) {
		start := strings.Index(template[i:], "{{")
		if start < 0 {
			b.WriteString(template[i:])
			break
		}
		b.WriteString(template[i : i+start])
		openIdx := i + start
		closeRel := strings.Index(template[openIdx+2:], "}}")
		if closeRel < 0 {
			b.WriteString(template[openIdx:])
			break
		}
		inner := template[openIdx+2 : openIdx+2+closeRel]
		advance := openIdx + 2 + closeRel + 2

		switch {
		case strings.HasPrefix(inner, "secret."):
			key := inner[len("secret."):]
			b.WriteString(secrets[key])
		case strings.HasPrefix(inner, "runtime."):
			key := inner[len("runtime."):]
			b.WriteString(runtime[key])
		default:
			// 非本 primitive 負責的 namespace,原樣寫回
			b.WriteString(template[openIdx:advance])
		}
		i = advance
	}
	return b.String()
}

func interpolateRecord(
	record map[string]string,
	secrets, runtime map[string]string,
) map[string]string {
	if record == nil {
		return map[string]string{}
	}
	result := make(map[string]string, len(record))
	for k, v := range record {
		result[k] = interpolateTemplate(v, secrets, runtime)
	}
	return result
}
