// auth_service_account — Google Service Account JWT auth primitive
//
// 讀取 auth_recipe:{service} + 解密 service_account_json + 組 JWT + RS256 簽章(透過 host)
// + token exchange → access_token + 展開 {{runtime.access_token}}。
//
// Host imports:
//   - u6u.kv_get              — 讀 RECIPES + CREDENTIALS_KV
//   - u6u.crypto_decrypt      — AES-GCM 解密 service account JSON
//   - u6u.crypto_sign_rs256   — RSASSA-PKCS1-v1_5 + SHA-256 (PKCS8 private key)
//   - u6u.http_request        — POST token exchange endpoint
//
//go:build tinygo

package main

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

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

//go:wasmimport u6u kv_get
func hostKvGet(
	keyPtr uintptr, keyLen uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

//go:wasmimport u6u crypto_decrypt
func hostCryptoDecrypt(
	encPtr uintptr, encLen uint32,
	ivPtr uintptr, ivLen uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

//go:wasmimport u6u crypto_sign_rs256
func hostCryptoSignRS256(
	dataPtr uintptr, dataLen uint32,
	pkcs8Ptr uintptr, pkcs8Len uint32,
	outPtr uintptr, outLenPtr uintptr,
) uint32

//go:wasmimport u6u http_request
func hostHttpRequest(
	urlPtr uintptr, urlLen uint32,
	methodPtr uintptr, methodLen uint32,
	headersPtr uintptr, headersLen uint32,
	bodyPtr uintptr, bodyLen 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"`
	// ResolvedSecrets：credential-store 遷移 T6/T7 方案 A（D19）——cypher-executor 從自己的
	// per-script secrets（新家）用 secret_get(D1 ref) 取到的明文，key = credential name
	// （對 SA 而言就是 required_secrets[0].key）。有值優先用，沒有才 fallback 舊 KV+crypto_decrypt。
	// 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"`
}

type TokenExchange struct {
	Endpoint string   `json:"endpoint"`
	Scopes   []string `json:"scopes"`
}

type AuthRecipe struct {
	Kind               string              `json:"kind"`
	Service            string              `json:"service"`
	Primitive          string              `json:"primitive"`
	ServiceAccountKind string              `json:"service_account_kind,omitempty"`
	TokenExchange      *TokenExchange      `json:"token_exchange,omitempty"`
	RequiredSecrets    []SecretRequirement `json:"required_secrets"`
	Inject             AuthInjectSpec      `json:"inject"`
}

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

type ServiceAccountJSON struct {
	ClientEmail string `json:"client_email"`
	PrivateKey  string `json:"private_key"`
}

type JWTHeader struct {
	Alg string `json:"alg"`
	Typ string `json:"typ"`
}

type JWTPayload struct {
	Iss   string `json:"iss"`
	Sub   string `json:"sub"`
	Aud   string `json:"aud"`
	Scope string `json:"scope"`
	Iat   int64  `json:"iat"`
	Exp   int64  `json:"exp"`
}

// ── 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
	}
	if input.Service == "" {
		writeError("service 必填")
		return
	}
	if input.Action != "" && input.Action != "authenticate" {
		writeError("auth_service_account 僅支援 action=authenticate")
		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 != "service_account" {
		writeError("auth recipe " + input.Service + " 的 primitive 不是 service_account (是 " + recipe.Primitive + ")")
		return
	}
	if recipe.ServiceAccountKind != "google_jwt" {
		writeError("auth recipe " + input.Service + " 的 service_account_kind 必須是 google_jwt,實際: " + recipe.ServiceAccountKind)
		return
	}
	if recipe.TokenExchange == nil || recipe.TokenExchange.Endpoint == "" {
		writeError("auth recipe " + input.Service + " 缺少 token_exchange.endpoint")
		return
	}
	if len(recipe.RequiredSecrets) == 0 {
		writeError("auth recipe " + input.Service + " 缺少 required_secrets[0](SA JSON)")
		return
	}

	// 2. 取得 service account JSON 明文 (慣例:required_secrets[0] 是 SA JSON)
	saReq := recipe.RequiredSecrets[0]

	var saJSONStr string
	// T6 方案 A：cypher 已從新家（per-script secrets）取到明文就直接用，不走 KV。
	// input.ResolvedSecrets 為 nil 時此 map 讀取回 ("", false)，行為與遷移前一致。
	if v, ok := input.ResolvedSecrets[saReq.Key]; ok {
		saJSONStr = v
	} else {
		// T7 雙讀 fallback：新家沒給 → 舊路徑 kv_get + crypto_decrypt。
		kvKey := input.APIKey + ":cred:" + saReq.Key
		encJSON, s := kvGet(kvKey)
		if s == 2 {
			writeError("缺少 credential: " + saReq.Key + " (" + saReq.Label + ")。修復: 編輯 credentials.yaml 後執行 acr creds push")
			return
		}
		if s != 0 {
			writeError("kv_get 失敗(credential " + saReq.Key + ")")
			return
		}

		var rec EncryptedRecord
		if err := json.Unmarshal([]byte(encJSON), &rec); err != nil {
			writeError("credential " + saReq.Key + " 格式錯誤: " + err.Error())
			return
		}
		decrypted, ok := cryptoDecrypt(rec.Encrypted, rec.IV)
		if !ok {
			writeError("credential " + saReq.Key + " 解密失敗")
			return
		}
		saJSONStr = decrypted
	}

	// 3. 解析 service account JSON
	var sa ServiceAccountJSON
	if err := json.Unmarshal([]byte(saJSONStr), &sa); err != nil {
		writeError("service account JSON 格式錯誤: " + err.Error())
		return
	}
	if sa.ClientEmail == "" || sa.PrivateKey == "" {
		writeError("service account JSON 缺少 client_email 或 private_key")
		return
	}

	// 4. PEM → PKCS8 bytes (去 header/footer + base64 decode)
	pkcs8, err := pemToPkcs8(sa.PrivateKey)
	if err != nil {
		writeError("解析 service account private key 失敗: " + err.Error())
		return
	}

	// 5. 組 JWT header + payload (base64url-encoded)
	now := time.Now().Unix()
	header := JWTHeader{Alg: "RS256", Typ: "JWT"}
	payload := JWTPayload{
		Iss:   sa.ClientEmail,
		Sub:   sa.ClientEmail,
		Aud:   recipe.TokenExchange.Endpoint,
		Scope: strings.Join(recipe.TokenExchange.Scopes, " "),
		Iat:   now,
		Exp:   now + 3600,
	}
	headerBytes, _ := json.Marshal(header)
	payloadBytes, _ := json.Marshal(payload)

	signingInput := base64.RawURLEncoding.EncodeToString(headerBytes) + "." +
		base64.RawURLEncoding.EncodeToString(payloadBytes)

	// 6. 呼叫 host 簽章 (RSASSA-PKCS1-v1_5 + SHA-256)
	signature, ok := cryptoSignRS256([]byte(signingInput), pkcs8)
	if !ok {
		writeError("JWT 簽章失敗(host function crypto_sign_rs256 回傳錯誤)")
		return
	}

	jwt := signingInput + "." + base64.RawURLEncoding.EncodeToString(signature)

	// 7. token exchange:POST form-urlencoded 到 token_exchange.endpoint
	form := url.Values{}
	form.Set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
	form.Set("assertion", jwt)
	formBody := form.Encode()

	headersJSON := `{"Content-Type":"application/x-www-form-urlencoded"}`

	respStr, ok := httpRequest(recipe.TokenExchange.Endpoint, "POST", headersJSON, formBody)
	if !ok {
		writeError("token exchange HTTP 失敗")
		return
	}

	var tokenResp struct {
		AccessToken string `json:"access_token"`
		Error       string `json:"error"`
		ErrorDesc   string `json:"error_description"`
	}
	if err := json.Unmarshal([]byte(respStr), &tokenResp); err != nil {
		writeError("token exchange 回應解析失敗: " + err.Error() + " (raw: " + respStr + ")")
		return
	}
	if tokenResp.AccessToken == "" {
		errMsg := tokenResp.Error
		if tokenResp.ErrorDesc != "" {
			errMsg += ": " + tokenResp.ErrorDesc
		}
		if errMsg == "" {
			errMsg = "access_token 為空 (raw: " + respStr + ")"
		}
		writeError("token exchange 失敗: " + errMsg)
		return
	}

	// 8. 展開模板 (service_account 不用 secret.*,只用 runtime.access_token)
	secrets := map[string]string{}
	runtime := map[string]string{"access_token": tokenResp.AccessToken}
	authHeaders := interpolateRecord(recipe.Inject.Header, secrets, runtime)
	authQuery := interpolateRecord(recipe.Inject.Query, secrets, runtime)
	authBody := interpolateRecord(recipe.Inject.Body, secrets, runtime)

	out, _ := json.Marshal(map[string]interface{}{
		"success":      true,
		"auth_headers": authHeaders,
		"auth_query":   authQuery,
		"auth_body":    authBody,
		"runtime":      runtime,
	})
	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)
}

// pemToPkcs8 從 PEM 取出 base64 body 再 decode 成 bytes。
// 支援 "BEGIN PRIVATE KEY" / "BEGIN RSA PRIVATE KEY"(SA JSON 幾乎都是前者)。
func pemToPkcs8(pem string) ([]byte, error) {
	// 移除所有 BEGIN/END 行與空白
	lines := strings.Split(pem, "\n")
	var b strings.Builder
	for _, line := range lines {
		l := strings.TrimSpace(line)
		if l == "" {
			continue
		}
		if strings.HasPrefix(l, "-----BEGIN") || strings.HasPrefix(l, "-----END") {
			continue
		}
		b.WriteString(l)
	}
	cleaned := strings.ReplaceAll(b.String(), "\\n", "") // 防呆:JSON-escaped newline
	return base64.StdEncoding.DecodeString(cleaned)
}

// 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
}

func cryptoDecrypt(encB64, ivB64 string) (string, bool) {
	encBytes := []byte(encB64)
	ivBytes := []byte(ivB64)
	outBuf := make([]byte, 65536)
	var outLen uint32

	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
}

// cryptoSignRS256 呼叫 host,回傳簽章 bytes
func cryptoSignRS256(data, pkcs8 []byte) ([]byte, bool) {
	if len(data) == 0 || len(pkcs8) == 0 {
		return nil, false
	}
	outBuf := make([]byte, 1024) // RSA-2048 簽章 = 256 bytes,1KB 綽綽有餘
	var outLen uint32

	status := hostCryptoSignRS256(
		uintptr(unsafe.Pointer(&data[0])), uint32(len(data)),
		uintptr(unsafe.Pointer(&pkcs8[0])), uint32(len(pkcs8)),
		uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
	)
	if status != 0 {
		return nil, false
	}
	return outBuf[:outLen], true
}

// httpRequest 呼叫 host,回傳 response body 字串(host 側把 status + body 串好)
func httpRequest(url, method, headersJSON, body string) (string, bool) {
	urlBytes := []byte(url)
	methodBytes := []byte(method)
	headersBytes := []byte(headersJSON)
	bodyBytes := []byte(body)

	if len(urlBytes) == 0 {
		return "", false
	}

	outBuf := make([]byte, 65536)
	var outLen uint32

	// bodyBytes 可能為空(GET),host function 允許 len=0
	var bodyPtr uintptr
	if len(bodyBytes) > 0 {
		bodyPtr = uintptr(unsafe.Pointer(&bodyBytes[0]))
	}
	var headersPtr uintptr
	if len(headersBytes) > 0 {
		headersPtr = uintptr(unsafe.Pointer(&headersBytes[0]))
	}

	status := hostHttpRequest(
		uintptr(unsafe.Pointer(&urlBytes[0])), uint32(len(urlBytes)),
		uintptr(unsafe.Pointer(&methodBytes[0])), uint32(len(methodBytes)),
		headersPtr, uint32(len(headersBytes)),
		bodyPtr, uint32(len(bodyBytes)),
		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 展開為空字串。
// 其他 namespace 的 {{...}} 原樣保留。
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:
			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
}
