// VoicerOnePBX Programmable Voice — Example 1: Auto Attendant (Go, stdlib only)
//
// The classic phone menu: greet, collect one digit, branch to a department.
// See ../python/app.py for the fully-commented reference; this is the same flow, terser.
//
// Run:  VOICER_SECRET=… VOICER_BASE_URL=http://<lan-ip>:8080 go run main.go
// Point the PBX endpoint's control URL at:  http://<lan-ip>:8080/ivr
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

var (
	secret = os.Getenv("VOICER_SECRET")                    // must match the PBX endpoint
	base   = envOr("VOICER_BASE_URL", "http://localhost:8080") // how the PBX reaches this app (for actionUrls)
	port   = envOr("PORT", "8080")
)

// A verb is a single-key object; a CCD is an ordered slice of them.
type Verb map[string]any
type CCD []Verb

// Event is the PBX→you request body (only the fields these handlers read).
type Event struct {
	Event    string `json:"event"`
	CallID   string `json:"callId"`
	From     string `json:"from"`
	To       string `json:"to"`
	Digits   string `json:"digits"`
	Speech   string `json:"speech"`
	Reason   string `json:"reason"`
}

// department maps a menu digit to (spoken confirmation, transfer verb).
var department = map[string]struct {
	phrase   string
	transfer Verb
}{
	"1": {"Connecting you to sales.", Verb{"route": map[string]any{"to": "sales"}}},
	"2": {"Connecting you to support.", Verb{"route": map[string]any{"to": "support"}}},
	"0": {"Connecting you to reception.", Verb{"route": map[string]any{"to": "100"}}},
	"9": {"Connecting your call.", Verb{"forward": map[string]any{"to": "+19055550134"}}},
}

// ── The handlers — this is the app ───────────────────────────────────────────

func handleIVR(e Event) CCD {
	return CCD{
		{"say": map[string]any{"text": "Thank you for calling Acme. " +
			"For sales press 1, for support press 2, for reception press 0, " +
			"or press 9 to reach our after-hours line."}},
		{"gather": map[string]any{"numDigits": 1, "timeout": 6, "actionUrl": base + "/ivr/main"}},
	}
}

func handleMain(e Event) CCD {
	if e.Reason == "hangup" {
		return CCD{} // caller gone
	}
	dep, ok := department[e.Digits]
	if e.Reason == "timeout" || !ok {
		return CCD{
			{"say": map[string]any{"text": "Sorry, I did not get that."}},
			{"redirect": map[string]any{"url": base + "/ivr"}},
		}
	}
	return CCD{{"say": map[string]any{"text": dep.phrase}}, dep.transfer}
}

var routes = map[string]func(Event) CCD{
	"/ivr":      handleIVR,
	"/ivr/main": handleMain,
}

// ── HTTP plumbing (generic; identical across the three examples) ──────────────

// verify checks the PBX's HMAC signature: sha256=hex(HMAC-SHA256(secret, ts + "." + body)).
func verify(body []byte, h http.Header) bool {
	if secret == "" {
		return true // dev mode — verification skipped (warned at startup)
	}
	ts := h.Get("X-Voicer-Timestamp")
	sent := trimPrefix(h.Get("X-Voicer-Signature"), "sha256=")
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(ts + "."))
	mac.Write(body)
	return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(sent))
}

func handle(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	if !verify(body, r.Header) {
		http.Error(w, `{"error":"bad signature"}`, http.StatusForbidden)
		return
	}
	fn, ok := routes[r.URL.Path]
	if !ok {
		http.Error(w, `{"error":"no such route"}`, http.StatusNotFound)
		return
	}
	var e Event
	_ = json.Unmarshal(body, &e)
	log.Printf("  → %s  event=%s digits=%q callId=%.8s", r.URL.Path, e.Event, e.Digits, e.CallID)

	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(fn(e)) // ← run the app logic
}

func main() {
	for p := range routes {
		http.HandleFunc(p, handle)
	}
	fmt.Printf("Auto-attendant sample on :%s  (control URL → %s/ivr)\n", port, base)
	if secret == "" {
		fmt.Println("  ⚠️  VOICER_SECRET is empty — signature verification is DISABLED (dev only).")
	}
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func envOr(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}

func trimPrefix(s, p string) string {
	if len(s) >= len(p) && s[:len(p)] == p {
		return s[len(p):]
	}
	return s
}
