package handlers

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"strings"

	"wa-assistant/backend/models"
)

type flowStep struct {
	flowResult
	nodeID string
}

func flowNodeRevision(node flowNode) string {
	raw, _ := json.Marshal(node)
	sum := sha256.Sum256(raw)
	return hex.EncodeToString(sum[:8])
}

func flowButtonID(nodeID string, node flowNode, key string) string {
	return "flow:v2:" + base64.RawURLEncoding.EncodeToString([]byte(nodeID)) + ":" + base64.RawURLEncoding.EncodeToString([]byte(strings.TrimSpace(key))) + ":" + flowNodeRevision(node)
}

func parseFlowButton(id string) (nodeID, key, revision string, ok bool) {
	parts := strings.Split(id, ":")
	if len(parts) != 5 || parts[0] != "flow" || parts[1] != "v2" {
		return "", "", "", false
	}
	node, err := base64.RawURLEncoding.DecodeString(parts[2])
	if err != nil {
		return "", "", "", false
	}
	value, err := base64.RawURLEncoding.DecodeString(parts[3])
	if err != nil {
		return "", "", "", false
	}
	return string(node), string(value), parts[4], len(node) > 0 && len(value) > 0
}

// advanceFlow is shared by WhatsApp and the dashboard simulator. It neither
// persists sessions nor sends messages, creates handoffs, or calls an AI model.
func advanceFlow(f models.Flow, s flowStructure, current, text, actionID string) (step flowStep) {
	defer func() {
		if step.handled {
			step.delayMin, step.delayMax = f.DelayMin, f.DelayMax
		}
	}()
	enter := func(id, prefix string) flowStep {
		if node, ok := s.Nodes[id]; ok {
			return flowStep{flowResult: renderFlowNodeResult(id, node, s.Root, f.DisplayMode, prefix), nodeID: id}
		}
		return flowStep{flowResult: flowResult{handled: true, reply: "Menu ini sudah berubah. Ketik *" + f.Trigger + "* untuk membuka menu terbaru."}}
	}
	button := strings.HasPrefix(actionID, "flow:")
	if !button && matchTrigger(text, f.Trigger, f.MatchType) {
		return enter(s.Root, "")
	}
	node, active := s.Nodes[current]
	if !active {
		if button {
			return enter(s.Root, "Sesi menu sudah berakhir atau diperbarui. Silakan pilih kembali.")
		}
		return flowStep{}
	}
	input := text
	if button {
		from, key, revision, ok := parseFlowButton(actionID)
		// Old IDs contain only a key and cannot identify the originating menu.
		// Show current options again rather than guessing which action was meant.
		if !ok || from != current || revision != flowNodeRevision(node) {
			return enter(current, "Pilihan ini berasal dari menu sebelumnya. Silakan pilih kembali dari menu berikut.")
		}
		input = key
	}
	value := strings.ToLower(strings.TrimSpace(input))
	if value == "keluar" || value == "batal" || value == "exit" {
		return flowStep{flowResult: flowResult{handled: true, reply: "Baik, menu ditutup. Ketik *" + f.Trigger + "* kapan saja untuk membukanya lagi."}}
	}
	if value == "0" || value == "kembali" || value == "back" {
		if parent, ok := parentFlowNode(s, current); ok {
			return enter(parent, "")
		}
		return flowStep{flowResult: flowResult{handled: true, reply: "Menu ditutup. Ketik *" + f.Trigger + "* untuk membuka kembali."}}
	}
	option, matched := matchOption(node, input)
	if !matched {
		if looksLikeFlowCode(input, actionID) {
			return enter(current, "Pilihan belum dikenali. Ketik kode atau pilih tombol yang tersedia.")
		}
		return flowStep{} // Free text returns to the normal conversation pipeline.
	}
	switch option.Action {
	case "goto":
		return enter(option.Target, "")
	case "handoff":
		return flowStep{flowResult: flowResult{handled: true, reply: flowHandoffReply(option.Reply), handoff: true}}
	case "reply_menu":
		return enter(current, option.Reply)
	default:
		return flowStep{flowResult: flowResult{handled: true, reply: option.Reply}, nodeID: current}
	}
}

func flowHandoffReply(configured string) string {
	if reply := strings.TrimSpace(configured); reply != "" {
		return reply
	}
	return humanFacingHoldReply
}
