package request_maker

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type RequestMaker interface {
	MakeRequest(request string) (*ResponseData, error)
}

type WebSocketClient struct {
	conn *websocket.Conn
}

type HttpRequestMaker struct {
	host      string
	transport *http.Transport
	client    *http.Client
}

type JsonMap map[string]interface{}
type StatusCode int

type ResponseData struct {
	Body       JsonMap
	StatusCode StatusCode
	StatusStr  string
	Duration   time.Duration
}

func (h *HttpRequestMaker) MakeRequest(request string) (*ResponseData, error) {
	startTime := time.Now()
	req, err := http.NewRequest("POST", h.host, strings.NewReader(request))
	if err != nil {
		return nil, errors.New("Error creating request: " + err.Error())
	}

	response, err := h.client.Do(req)
	requestDuration := time.Since(startTime)

	if err != nil {
		return nil, errors.New("Error making request: " + err.Error())
	}

	body, err := io.ReadAll(response.Body)
	response.Body.Close()
	if err != nil {
		return nil, errors.New("Error reading response body: " + err.Error())
	}

	if response.StatusCode != 200 {
		return &ResponseData{StatusCode: StatusCode(response.StatusCode), StatusStr: response.Status + ": " + string(body)}, nil
	}

	var bodyParsed JsonMap
	err = json.Unmarshal(body, &bodyParsed)
	if err != nil {
		return nil, errors.New("Error parsing response '" + string(body) + "': " + err.Error())
	}

	return &ResponseData{bodyParsed, StatusCode(response.StatusCode), response.Status, requestDuration}, nil
}

func NewHttp(host string, port uint) *HttpRequestMaker {
	if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
		host = "http://" + host
	}
	transport := http.DefaultTransport.(*http.Transport).Clone()
	transport.DisableKeepAlives = true
	client := &http.Client{Transport: transport}

	return &HttpRequestMaker{host + ":" + fmt.Sprintf("%d", port), transport, client}
}

func NewWebSocketClient(host string, port uint) (*WebSocketClient, error) {
	var u url.URL
	if !strings.HasPrefix(host, "ws://") && !strings.HasPrefix(host, "wss://") {
		u = url.URL{Scheme: "ws", Host: host + ":" + fmt.Sprintf("%d", port), Path: "/"}
	} else {
		u = url.URL{Host: host + ":" + fmt.Sprintf("%d", port), Path: "/"}
	}
	conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		return nil, errors.New("Error connecting to WebSocket: " + err.Error())
	}
	return &WebSocketClient{conn: conn}, nil
}

// SendMessage sends a message to the WebSocket server
func (ws *WebSocketClient) SendMessage(message string) (*ResponseData, error) {
	defer ws.conn.Close()
	start := time.Now()
	err := ws.conn.WriteMessage(websocket.TextMessage, []byte(message))
	if err != nil {
		return nil, errors.New("Error sending ws message: " + err.Error())
	}

	var msg []byte
	err = ws.conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	if err != nil {
		return nil, errors.New("Error setting timeout: " + err.Error())
	}
	_, msg, err = ws.conn.ReadMessage()
	if err != nil {
		return nil, errors.New("Error reading message: " + err.Error())
	}
	requestDuration := time.Since(start)
	ws.conn.Close()

	var response JsonMap
	err = json.Unmarshal(msg, &response)
	if err != nil {
		return nil, errors.New("Error unmarshalling message: " + err.Error())
	}
	return &ResponseData{response, StatusCode(200), "WS Ok", requestDuration}, nil
}

func (ws *WebSocketClient) Close() error {
	return ws.conn.Close()
}
