package domain

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"
)

func parseURL(rawURL string) (*url.URL, error) {
	u, err := url.ParseRequestURI(rawURL)
	if err != nil {
		return nil, InvalidURLError{
			URL: rawURL,
		}
	}

	return u, nil
}

const (
	cursorSeparator         = ","
	cursorKeyValueSeparator = "="
)

type keyValuePair struct {
	key   string
	value any
}

func (kvp keyValuePair) valueString() string {
	switch v := kvp.value.(type) {
	case string:
		return v
	case int:
		return strconv.Itoa(v)
	case bool:
		return strconv.FormatBool(v)
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	case time.Time:
		return v.Format(time.RFC3339)
	case fmt.Stringer:
		return v.String()
	default:
		return fmt.Sprintf("%v", v)
	}
}

func encodeCursor(kvps []keyValuePair) string {
	if len(kvps) == 0 {
		return ""
	}

	values := make([]string, len(kvps))
	n := len(cursorSeparator) * (len(kvps) - 1)

	for i, kvp := range kvps {
		value := kvp.valueString()
		n += len(kvp.key) + len(value) + len(cursorKeyValueSeparator)
		values[i] = value
	}

	var b bytes.Buffer
	b.Grow(n)

	for i, kvp := range kvps {
		if b.Len() > 0 {
			_, _ = b.WriteString(cursorSeparator)
		}

		_, _ = b.WriteString(kvp.key)
		_, _ = b.WriteString(cursorKeyValueSeparator)
		_, _ = b.WriteString(values[i])
	}

	return base64.StdEncoding.EncodeToString(b.Bytes())
}

type cursorKeyNotFoundError struct {
	key string
}

func (e cursorKeyNotFoundError) Error() string {
	return "key " + e.key + " not found"
}

type decodedCursor map[string]string

const (
	encodedCursorMinLength = 1
	encodedCursorMaxLength = 1000
)

func decodeCursor(s string) (decodedCursor, error) {
	if err := validateStringLen(s, encodedCursorMinLength, encodedCursorMaxLength); err != nil {
		return nil, err
	}

	decodedBytes, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return nil, ErrInvalidCursor
	}
	decoded := string(decodedBytes)

	kvps := strings.Split(decoded, cursorSeparator)

	m := make(decodedCursor, len(kvps))

	for _, kvp := range kvps {
		k, v, found := strings.Cut(kvp, cursorKeyValueSeparator)
		if !found {
			return nil, ErrInvalidCursor
		}
		m[k] = v
	}

	return m, nil
}

func (c decodedCursor) string(k string) (string, error) {
	val, ok := c[k]
	if !ok {
		return "", cursorKeyNotFoundError{k}
	}

	return val, nil
}

func (c decodedCursor) int(k string) (int, error) {
	valStr, ok := c[k]
	if !ok {
		return 0, cursorKeyNotFoundError{k}
	}

	val, err := strconv.Atoi(valStr)
	if err != nil {
		return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
	}

	return val, nil
}

func (c decodedCursor) float64(k string) (float64, error) {
	valStr, ok := c[k]
	if !ok {
		return 0, cursorKeyNotFoundError{k}
	}

	val, err := strconv.ParseFloat(valStr, 64)
	if err != nil {
		return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
	}

	return val, nil
}

func (c decodedCursor) bool(k string) (bool, error) {
	valStr, ok := c[k]
	if !ok {
		return false, cursorKeyNotFoundError{k}
	}

	val, err := strconv.ParseBool(valStr)
	if err != nil {
		return false, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
	}

	return val, nil
}

func (c decodedCursor) time(k string) (time.Time, error) {
	valStr, ok := c[k]
	if !ok {
		return time.Time{}, cursorKeyNotFoundError{k}
	}

	val, err := time.Parse(time.RFC3339, valStr)
	if err != nil {
		return time.Time{}, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
	}

	return val, nil
}
