package web

import (
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws/arn"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	csrf "github.com/utrack/gin-csrf"
	"golang.org/x/exp/maps"

	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
)

type (
	// Level is an information level: danger | warning.
	Level string
	// QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
	QueueOp string
)

const (
	LevelDanger  Level = "danger"
	LevelInfo    Level = "info"
	LevelWarning Level = "warning"

	OpDelete  QueueOp = "delete"
	OpInvalid QueueOp = "invalid"
	OpPurge   QueueOp = "purge"
	OpRedrive QueueOp = "redrive"
)

var confirms = map[QueueOp]struct {
	question, description string
	confirm               string
	Level                 // danger|warning
}{
	OpDelete: {
		confirm:     "Clear",
		description: "These messages cannot be recovered after that step",
		question:    "Do you confirm this deletion request?",
		Level:       LevelDanger,
	},
	OpInvalid: {
		description: "The operation requested is invalid",
		Level:       LevelInfo,
	},
	OpPurge: {
		confirm:     "Purge",
		description: "All the messages in the letter queue will be lost after that step",
		question:    "Do you confirm this purge request?",
		Level:       LevelDanger,
	},
	OpRedrive: {
		confirm:     "Redrive",
		description: "These messages will be in their source queue after that step",
		question:    "Do you confirm this redriving request?",
		Level:       LevelWarning,
	},
}

// getOp extracts the operation from the unsafe form data, returning a safe value.
func getOp(u *url.URL) QueueOp {
	q := u.Query()
	found := 0
	var res QueueOp
	for key := range q {
		switch op := QueueOp(strings.ToLower(key)); op {
		case OpDelete, OpPurge, OpRedrive:
			res = op
			found++
		default:
			continue
		}
	}
	if found != 1 {
		return OpInvalid
	}
	return res
}

type IDValidator func(in string) bool

// validateUint is an IDValidator for unsigned integer strings.
func validateUint(in string) bool {
	_, err := strconv.Atoi(in)
	return err == nil
}

// validateUUID is an IDValidator for UUID strings
func validateUUID(in string) bool {
	_, err := uuid.Parse(in)
	return err == nil
}

// validateARN is an IDValidator for ARN strings
func validateARN(in string) bool {
	return arn.IsARN(in)
}

func parseIDs(v url.Values, isValid IDValidator) []string {
	const prefix = "id-"
	var ids []string
	for k, vs := range v {
		// Weed out bad keys, bad value lengths, and non-matching checkbox statuses.
		if pos := strings.Index(k, prefix); pos == -1 || len(vs) != 1 || vs[0] != "on" {
			continue
		}
		b, a, found := strings.Cut(k, prefix)
		if b == "" && found && isValid(a) {
			ids = append(ids, a)
		}
	}
	return ids
}

func parseMessages(v url.Values, ids []string) []redriver.Message {
	m := make(map[int]redriver.Message, len(ids))
	keyRx := regexp.MustCompile("^(id|mid|rh)-(.+)")
	mOK := make(map[int]bool, len(ids))
	for k, vs := range v {
		ms := keyRx.FindStringSubmatch(k)
		if len(ms) != 3 || len(vs) != 1 {
			continue
		}
		v := vs[0]

		id, err := strconv.Atoi(ms[2])
		if err != nil {
			continue
		}
		message := m[id]
		switch ms[1] {
		case "id":
			if v == "on" {
				mOK[id] = true
			}
		case "rh":
			message.ReceiptHandle = v
		case "mid":
			message.MessageId = v
		}
		m[id] = message
	}

	// Only return selected messages.
	res := make(map[int]redriver.Message, len(mOK))
	for id := range mOK {
		res[id] = m[id]
	}
	return maps.Values(res)
}

func makeConfirmHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		qName := c.Param("name")
		u := c.Request.URL
		sess := sessions.Default(c)
		flashes := sess.Flashes()
		defer func() {
			_ = sess.Save()
		}()

		op := getOp(u)
		ids := parseIDs(u.Query(), validateUint)
		var messages []redriver.Message
		if len(ids) == 0 && op != OpPurge {
			op = OpInvalid
			messages = nil
		} else {
			messages = parseMessages(u.Query(), ids)
		}

		token := csrf.GetToken(c)

		data := confirms[op] // op is guaranteed to be valid by getOp().

		h := gin.H{
			"action":      "/queue/" + qName + "/" + string(op),
			"cancel":      "Cancel",
			"cancelURL":   "/queue/" + qName,
			"confirm":     data.confirm,
			"csrf":        token,
			"description": data.description,
			"flashes":     flashes,
			"level":       data.Level,
			"messages":    messages,
			"question":    data.question,
		}
		if qName == "" {
			h["cancelURL"] = "/"
		}
		if op == OpInvalid {
			h["messages"] = nil
		}
		c.HTML(http.StatusOK, "confirm", h)
	}
}
