// zeq_client.go — single-file Go SDK for the Zeq OS HTTP API (v1.1.3 contract).
//
// Zero third-party dependencies. Standard library only. Runs out of the box:
//
//	go run zeq_client.go                    # requires env ZEQ_TOKEN=zeq_ak_...
//
// To use it as a library, change `package main` to `package zeq` and delete the
// `main()` block at the bottom.
//
// Mirrors zeq_client.py and zeq_client.js:
//
//	POST /api/zeq/compute  { operator_id, inputs } -> numeric result
//	POST /api/zeq/prove    { operator_id, inputs } -> ZeqProof HMAC
//
// [Zeq OS Daemon] HulyaPulse 1.287 Hz — τ_zeqond = 0.777 s — α_K = 0.00129
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	AlphaSpec = 0.00129
	FHulya    = 1.287
	TauZeqond = 0.777
)

// ZeqError wraps a non-2xx response or transport failure.
type ZeqError struct {
	Status  int    `json:"status"`
	Code    string `json:"code"`
	Message string `json:"message"`
	Body    string `json:"body,omitempty"`
}

func (e *ZeqError) Error() string {
	return fmt.Sprintf("zeq: %s (status=%d code=%s)", e.Message, e.Status, e.Code)
}

// ComputeResult is a typed view over the most-used response fields.
// The full JSON map is also available via Raw.
type ComputeResult struct {
	Value          float64        `json:"value"`
	Unit           string         `json:"unit"`
	Uncertainty    float64        `json:"uncertainty"`
	OperatorID     string         `json:"operator_id"`
	Solver         string         `json:"solver"`
	BindingOverlay string         `json:"binding_overlay"`
	ZeqProof       string         `json:"zeqProof"`
	Compliance     map[string]any `json:"compliance,omitempty"`
	ZeqState       map[string]any `json:"zeqState,omitempty"`
	Cko            map[string]any `json:"cko,omitempty"`
	Raw            map[string]any `json:"-"`
}

// Client is the Zeq OS API client.
type Client struct {
	Token   string
	BaseURL string
	HTTP    *http.Client
}

// New returns a Client with sensible defaults. Token defaults to $ZEQ_TOKEN.
func New(token string) *Client {
	if token == "" {
		token = os.Getenv("ZEQ_TOKEN")
	}
	return &Client{
		Token:   token,
		BaseURL: "https://www.zeq.dev",
		HTTP:    &http.Client{Timeout: 10 * time.Second},
	}
}

func (c *Client) post(ctx context.Context, path string, body any) (map[string]any, error) {
	if c.Token == "" {
		return nil, errors.New("zeq: missing token (set ZEQ_TOKEN or pass to New)")
	}
	buf, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	url := strings.TrimRight(c.BaseURL, "/") + path
	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(buf))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.Token)

	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)

	var out map[string]any
	_ = json.Unmarshal(raw, &out)

	if resp.StatusCode >= 400 {
		msg := fmt.Sprintf("HTTP %d", resp.StatusCode)
		code := "HTTP_ERROR"
		if eObj, ok := out["error"].(map[string]any); ok {
			if m, ok := eObj["message"].(string); ok {
				msg = m
			}
			if cd, ok := eObj["code"].(string); ok {
				code = cd
			}
		}
		return nil, &ZeqError{Status: resp.StatusCode, Code: code, Message: msg, Body: string(raw)}
	}
	return out, nil
}

// Compute executes an operator and returns the typed ComputeResult.
func (c *Client) Compute(ctx context.Context, operatorID string, inputs map[string]any) (*ComputeResult, error) {
	raw, err := c.post(ctx, "/api/zeq/compute", map[string]any{
		"operator_id": operatorID,
		"inputs":      inputs,
	})
	if err != nil {
		return nil, err
	}
	return unpack(raw), nil
}

// Prove is /api/zeq/prove — same payload, returns a signed ZeqProof envelope.
func (c *Client) Prove(ctx context.Context, operatorID string, inputs map[string]any) (*ComputeResult, error) {
	raw, err := c.post(ctx, "/api/zeq/prove", map[string]any{
		"operator_id": operatorID,
		"inputs":      inputs,
	})
	if err != nil {
		return nil, err
	}
	return unpack(raw), nil
}

func unpack(raw map[string]any) *ComputeResult {
	r := &ComputeResult{Raw: raw}
	if v, ok := raw["value"].(float64); ok {
		r.Value = v
	}
	if v, ok := raw["unit"].(string); ok {
		r.Unit = v
	}
	if v, ok := raw["uncertainty"].(float64); ok {
		r.Uncertainty = v
	}
	if v, ok := raw["operator_id"].(string); ok {
		r.OperatorID = v
	}
	if v, ok := raw["solver"].(string); ok {
		r.Solver = v
	}
	if v, ok := raw["binding_overlay"].(string); ok {
		r.BindingOverlay = v
	}
	if v, ok := raw["zeqProof"].(string); ok {
		r.ZeqProof = v
	}
	if v, ok := raw["compliance"].(map[string]any); ok {
		r.Compliance = v
	}
	if v, ok := raw["zeqState"].(map[string]any); ok {
		r.ZeqState = v
	}
	if v, ok := raw["cko"].(map[string]any); ok {
		r.Cko = v
	}
	return r
}

// ── CLI smoke test (delete this block when using as a library) ────────────

func main() {
	c := New("")
	res, err := c.Compute(context.Background(), "NM21", map[string]any{
		"m1": 5.972e24, "m2": 7.342e22, "r": 3.844e8,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
	fmt.Println(strings.Repeat("=", 70))
	fmt.Println("NM21 — Earth-Moon gravitational force  (expected ~ 1.98e20 N)")
	fmt.Println(strings.Repeat("=", 70))
	fmt.Printf("value           = %g\n", res.Value)
	fmt.Printf("unit            = %s\n", res.Unit)
	fmt.Printf("uncertainty     = %g\n", res.Uncertainty)
	fmt.Printf("solver          = %s\n", res.Solver)
	fmt.Printf("binding_overlay = %s\n", res.BindingOverlay)
	proof := res.ZeqProof
	if len(proof) > 24 {
		proof = proof[:24] + "…"
	}
	fmt.Printf("zeqProof        = %s\n", proof)
}
