// VoicerOnePBX Programmable Voice — Example 2: Phone Survey (Go, stdlib only)
//
// A multi-step flow that keeps state across callbacks, keyed by the stable callId.
// 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/survey
package main

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

var (
	secret = os.Getenv("VOICER_SECRET")
	base   = envOr("VOICER_BASE_URL", "http://localhost:8080")
	port   = envOr("PORT", "8080")
)

type Verb map[string]any
type CCD []Verb

type Event struct {
	Event      string `json:"event"`
	CallID     string `json:"callId"`
	From       string `json:"from"`
	Digits     string `json:"digits"`
	Reason     string `json:"reason"`
	Transcript string `json:"transcript"`
}

// Per-call state, keyed by callId. In production this is a DB row; the KEY is what matters.
type response struct {
	From, Rating, Recommend, Comment string
}

var (
	mu        sync.Mutex
	responses = map[string]*response{}
)

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

func handleStart(e Event) CCD {
	mu.Lock()
	responses[e.CallID] = &response{From: e.From}
	mu.Unlock()
	return CCD{
		{"say": map[string]any{"text": "Thanks for taking our two-question survey. " +
			"On a scale of 1 to 5, how satisfied were you with your call today? " +
			"Press a number from 1 to 5."}},
		{"gather": map[string]any{"numDigits": 1, "timeout": 8, "actionUrl": base + "/survey/q2"}},
	}
}

func handleQ2(e Event) CCD {
	mu.Lock()
	rec := responses[e.CallID]
	if rec != nil && e.Digits != "" {
		rec.Rating = e.Digits // ← state carried forward by callId
	}
	mu.Unlock()
	if rec == nil || e.Reason == "hangup" {
		return CCD{}
	}
	return CCD{
		{"say": map[string]any{"text": "Thank you. Would you recommend us to a friend? Press 1 for yes, 2 for no."}},
		{"gather": map[string]any{"numDigits": 1, "timeout": 8, "actionUrl": base + "/survey/q3"}},
	}
}

func handleQ3(e Event) CCD {
	mu.Lock()
	rec := responses[e.CallID]
	if rec != nil {
		rec.Recommend = map[string]string{"1": "yes", "2": "no"}[e.Digits]
		if rec.Recommend == "" {
			rec.Recommend = "unknown"
		}
	}
	mu.Unlock()
	if rec == nil || e.Reason == "hangup" {
		return CCD{}
	}
	return CCD{
		{"say": map[string]any{"text": "Last thing: leave any comments after the tone, then press pound. " +
			"Or press pound now to finish."}},
		{"record": map[string]any{"maxSeconds": 30, "finishOnKey": "#", "transcribe": true, "playBeep": true,
			"actionUrl": base + "/survey/done"}},
	}
}

func handleDone(e Event) CCD {
	mu.Lock()
	rec := responses[e.CallID]
	delete(responses, e.CallID)
	mu.Unlock()
	if rec != nil {
		rec.Comment = e.Transcript
		out, _ := json.Marshal(rec)
		log.Printf("  ✅ SURVEY COMPLETE: %s", out) // replace with a DB insert
	}
	return CCD{{"say": map[string]any{"text": "Thank you for your feedback. Goodbye."}}, {"hangup": map[string]any{}}}
}

var routes = map[string]func(Event) CCD{
	"/survey":      handleStart,
	"/survey/q2":   handleQ2,
	"/survey/q3":   handleQ3,
	"/survey/done": handleDone,
}

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

func verify(body []byte, h http.Header) bool {
	if secret == "" {
		return true
	}
	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))
}

func main() {
	for p := range routes {
		http.HandleFunc(p, handle)
	}
	fmt.Printf("Survey sample on :%s  (control URL → %s/survey)\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
}
