package ezhttp

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"io"
	"net/http"
	urlpkg "net/url"
	"strings"
)

type RequestModifier func(r *RequestBuilder) *RequestBuilder
type ValueProvider[T any] func() T
type AuthProvider ValueProvider[string]
type RequestBodyProvider func() (io.Reader, string)

func Request(mods ...RequestModifier) *http.Request {
	r := NewRequestBuilder()
	for _, mod := range mods {
		r = mod(r)
	}
	return r.Build()
}

func Template(req *http.Request) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		return NewRequestBuilderFromRequest(req)
	}
}

func URL(url string) RequestModifier {
	parsedUrl, err := urlpkg.Parse(url)
	if err != nil {
		panic(err)
	}

	return func(r *RequestBuilder) *RequestBuilder {
		r.URL = parsedUrl
		return r
		//return Headers("Host", parsedUrl.Host)(r)
	}
}

func Scheme(scheme string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.URL.Scheme = scheme
		return r
	}
}

func Host(host string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.URL.Host = host
		return r
	}
}

func Path(path string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.URL.Path = path
		return r
	}
}

func AppendPath(path ...string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.URL = r.URL.JoinPath(path...)
		return r
	}
}

func Fragment(fragment string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.URL.Fragment = fragment
		return r
	}
}

func Query(keyValuePairs ...string) RequestModifier {
	if len(keyValuePairs)%2 != 0 {
		panic("keyValuePairs must have an even length")
	}

	return func(r *RequestBuilder) *RequestBuilder {
		q := r.URL.Query()
		for i := 0; i < len(keyValuePairs)-1; i += 2 {
			q.Add(keyValuePairs[i], keyValuePairs[i+1])
		}
		r.URL.RawQuery = q.Encode()
		return r
	}
}

func RemoveQuery(keys ...string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		q := r.URL.Query()
		for _, key := range keys {
			q.Del(key)
		}
		r.URL.RawQuery = q.Encode()
		return r
	}
}

func QueryMap(query map[string]string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		q := r.URL.Query()
		for key, value := range query {
			q.Add(key, value)
		}
		r.URL.RawQuery = q.Encode()
		return r
	}
}

func Method(method string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.Method = method
		return r
	}
}

func AddHeaders(keyValuePairs ...string) RequestModifier {
	if len(keyValuePairs)%2 != 0 {
		panic("keyValuePairs must have an even length")
	}

	return func(r *RequestBuilder) *RequestBuilder {
		for i := 0; i < len(keyValuePairs)-1; i += 2 {
			r.Header.Add(keyValuePairs[i], keyValuePairs[i+1])
		}
		return r
	}
}

func RemoveHeaders(headers ...string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		for _, header := range headers {
			r.Header.Del(header)
		}
		return r
	}
}

func RemoveAllHeaders() RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		r.Header = make(http.Header)
		return r
	}
}

func Headers(keyValuePairs ...string) RequestModifier {
	return chainMods(
		RemoveAllHeaders(),
		AddHeaders(keyValuePairs...),
	)
}

func AddHeadersMap(headers map[string]string) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		for key, value := range headers {
			r.Header.Add(key, value)
		}
		return r
	}
}

func HeadersMap(headers map[string]string) RequestModifier {
	return chainMods(
		RemoveAllHeaders(),
		AddHeadersMap(headers),
	)
}

func Auth(auth AuthProvider) RequestModifier {
	return chainMods(
		RemoveHeaders("Authorization"),
		AddHeaders("Authorization", auth()),
	)
}

func Basic(username, password string) AuthProvider {
	return func() string {
		return "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
	}
}

func Bearer(token string) AuthProvider {
	return func() string {
		return "Bearer " + token
	}
}

func ContentType(contentType string) RequestModifier {
	return Headers("Content-Type", contentType)
}

func Body(bodyProvider RequestBodyProvider) RequestModifier {
	return func(r *RequestBuilder) *RequestBuilder {
		body, contentType := bodyProvider()
		r.Body = body
		if contentType != "" && r.Header.Get("Content-Type") != "" {
			r.Header.Set("Content-Type", contentType)
		}
		return r
	}
}

func Reader(r io.Reader) RequestBodyProvider {
	return func() (io.Reader, string) {
		return r, "application/octet-stream"
	}
}

func String(s string) RequestBodyProvider {
	return func() (io.Reader, string) {
		return strings.NewReader(s), "text/plain"
	}
}

func Bytes(b []byte) RequestBodyProvider {
	return func() (io.Reader, string) {
		return bytes.NewReader(b), "application/octet-stream"
	}
}

func JSON[T any](value T) RequestBodyProvider {
	buf := bytes.NewBuffer(make([]byte, 0, 1024))
	if err := json.NewEncoder(buf).Encode(value); err != nil {
		panic(err)
	}

	return func() (io.Reader, string) {
		return buf, "application/json"
	}
}