package flow

import (
	"fmt"
	"iter"
	"net/http"
	"net/url"

	"go.starlark.net/starlark"
)

func httpHeaderToDict(h http.Header) (*starlark.Dict, error) {
	d := starlark.NewDict(len(h))

	for k, v := range h {
		vals := make([]starlark.Value, 0, len(v))
		for _, val := range v {
			vals = append(vals, starlark.String(val))
		}
		err := d.SetKey(starlark.String(k), starlark.NewList(vals))
		if err != nil {
			return nil, err
		}
	}

	d.Freeze()
	return d, nil
}

func dictToHTTPHeader(d *starlark.Dict) (http.Header, error) {
	if d == nil || d.Len() == 0 {
		return make(http.Header), nil
	}
	res := make(http.Header, d.Len())
	for kVal, vVal := range d.Entries() {
		key, ok := kVal.(starlark.String)
		if !ok {
			return nil, fmt.Errorf("key must be a string, got %T", key)
		}

		var resVal []string
		switch val := vVal.(type) {
		case *starlark.List:
			var err error
			resVal, err = sequenceToStringSlice(val)
			if err != nil {
				return nil, err
			}
		case starlark.String:
			resVal = []string{string(val)}
		default:
			return nil, fmt.Errorf("value must be a string or a list of strings, got %T", vVal)
		}
		res[string(key)] = resVal
	}

	return res, nil
}

func dictToURLValues(d *starlark.Dict) (url.Values, error) {
	if d == nil || d.Len() == 0 {
		return nil, nil
	}

	res := make(url.Values, d.Len())
	for kVal, vVal := range d.Entries() {
		key, ok := kVal.(starlark.String)
		if !ok {
			return nil, fmt.Errorf("key must be a string, got %T", kVal)
		}

		var resVal []string
		var err error
		switch val := vVal.(type) { // check for concrete types as we don't want to allow Dict as Sequence
		case *starlark.Set:
			resVal, err = sequenceToStringSlice(val)
			if err != nil {
				return nil, err
			}
		case *starlark.List:
			resVal, err = sequenceToStringSlice(val)
			if err != nil {
				return nil, err
			}
		case starlark.Tuple:
			resVal, err = sequenceToStringSlice(val)
			if err != nil {
				return nil, err
			}
		case starlark.String:
			resVal = []string{string(val)}
		default:
			return nil, fmt.Errorf("value must be a string or a list of strings, got %T", vVal)
		}
		res[string(key)] = resVal
	}

	return res, nil
}

type seq interface {
	Len() int
	Elements() iter.Seq[starlark.Value]
}

func sequenceToStringSlice(s seq) ([]string, error) {
	res := make([]string, 0, s.Len())
	for item := range s.Elements() {
		itemStr, ok := item.(starlark.String)
		if !ok {
			return nil, fmt.Errorf("value must be a string, got %T", item)
		}
		res = append(res, string(itemStr))
	}
	return res, nil
}
