package tools

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

// HTTP Method Enum defines the different methods that can be used to interact with the HTTP server
type METHOD int

const (
	GET METHOD = iota
	PUT
	POST
	POSTCHECK
	DELETE

	STRICT_INTERNAL_GET
	STRICT_INTERNAL_PUT
	STRICT_INTERNAL_POST
	STRICT_INTERNAL_DELETE
)

// String returns the string of the enum
func (m METHOD) String() string {
	return [...]string{"GET", "PUT", "POST", "POST", "DELETE", "INTERNALGET", "INTERNALPUT", "INTERNALPOST", "INTERNALDELETE"}[m]
}

// EnumIndex returns the index of the enum
func (m METHOD) EnumIndex() int {
	return int(m)
}

// ToMethod returns the method from a string
func ToMethod(str string) METHOD {
	for _, s := range []METHOD{GET, PUT, POST, POSTCHECK, DELETE,
		STRICT_INTERNAL_GET, STRICT_INTERNAL_PUT, STRICT_INTERNAL_POST, STRICT_INTERNAL_DELETE} {
		if s.String() == str {
			return s
		}
	}
	return GET
}

var HTTPCallerInstance = &HTTPCaller{} // Singleton instance of the HTTPCaller

type HTTPCaller struct {
	URLS     map[DataType]map[METHOD]string // Map of the different methods and their urls
	Disabled bool                           // Disabled flag
}

// NewHTTPCaller creates a new instance of the HTTP Caller
func NewHTTPCaller(urls map[DataType]map[METHOD]string) *HTTPCaller {
	return &HTTPCaller{
		URLS:     urls, // Set the urls defined in the config & based on the data name type & method
		Disabled: false,
	}
}

// CallGet calls the GET method on the HTTP server
func (caller *HTTPCaller) CallGet(url string, subpath string, types ...string) ([]byte, error) {
	req, err := http.NewRequest(http.MethodGet, url+subpath, bytes.NewBuffer([]byte("")))
	if err != nil {
		return nil, err
	}
	for _, t := range types {
		req.Header.Set("Content-Type", t)
	}
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// CallPut calls the DELETE method on the HTTP server
func (caller *HTTPCaller) CallDelete(url string, subpath string) ([]byte, error) {
	resp, err := http.NewRequest("DELETE", url+subpath, nil)
	if err != nil || resp == nil || resp.Body == nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// CallPost calls the POST method on the HTTP server
func (caller *HTTPCaller) CallPost(url string, subpath string, body interface{}, types ...string) ([]byte, error) {
	postBody, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	responseBody := bytes.NewBuffer(postBody)
	contentType := "application/json"
	if len(types) > 0 {
		contentType = types[0]
	}
	resp, err := http.Post(url+subpath, contentType, responseBody)
	if err != nil || resp == nil || resp.Body == nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// CallPost calls the POST method on the HTTP server
func (caller *HTTPCaller) CallPut(url string, subpath string, body map[string]interface{}) ([]byte, error) {
	postBody, _ := json.Marshal(body)
	responseBody := bytes.NewBuffer(postBody)
	req, err := http.NewRequest(http.MethodPut, url+subpath, responseBody)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// CallRaw calls the Raw method on the HTTP server
func (caller *HTTPCaller) CallRaw(method string, url string, subpath string,
	body map[string]interface{}, content_type string, fakeTLSTermination bool, cookies ...*http.Cookie) (*http.Response, error) {
	postBody, _ := json.Marshal(body)
	responseBody := bytes.NewBuffer(postBody)
	req, err := http.NewRequest(method, url+subpath, responseBody)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", content_type)
	if fakeTLSTermination {
		req.Header.Add("X-Forwarded-Proto", "https")
	}
	for _, c := range cookies {
		req.AddCookie(c)
	}
	client := &http.Client{}
	return client.Do(req)
}

// CallRaw calls the Raw method on the HTTP server
func (caller *HTTPCaller) CallForm(method string, url string, subpath string,
	body url.Values, content_type string, fakeTLSTermination bool, cookies ...*http.Cookie) (*http.Response, error) {
	req, err := http.NewRequest(method, url+subpath, strings.NewReader(body.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", content_type)
	if fakeTLSTermination {
		req.Header.Add("X-Forwarded-Proto", "https")
	}
	for _, c := range cookies {
		req.AddCookie(c)
	}
	client := &http.Client{}
	return client.Do(req)
}