package models

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var timeLayout = "Mon Jan  2 15:04:05 2006"

type CoD4Server struct {
	server   string
	port     string
	protocol string
	timeout  time.Duration
}

type CoD4ServerStatus struct {
	raw          string
	serverData   map[string]string
	MapStartTime time.Time
	MapName      string
	Score        []Score
	meta         map[string]interface{}
}

func NewCOD4ServerStatus(server, port string, timeout time.Duration) (*CoD4Server, error) {
	if server == "" || port == "" {
		return nil, errors.New("server or port is empty")
	}

	return &CoD4Server{
		server:   server,
		port:     port,
		protocol: "udp",
		timeout:  timeout,
	}, nil
}

func (c *CoD4Server) GetServerStatus() (*CoD4ServerStatus, error) {
	data, err := c.receiveData()
	if err != nil {
		return nil, err
	}

	return parseServerData(data)
}

func (c *CoD4Server) receiveData() (string, error) {
	address := fmt.Sprintf("%s:%s", c.server, c.port)
	conn, err := net.DialTimeout(c.protocol, address, c.timeout)
	if err != nil {
		return "", fmt.Errorf("could not connect to server: %s", err)
	}
	defer conn.Close()

	conn.SetDeadline(time.Now().Add(c.timeout))
	_, err = conn.Write([]byte("\xFF\xFF\xFF\xFFgetstatus\x00"))
	if err != nil {
		return "", fmt.Errorf("failed to send data to server: %s", err)
	}

	var buf bytes.Buffer
	response := make([]byte, 8192)
	reader := bufio.NewReader(conn)
	for {
		n, err := reader.Read(response)
		if err != nil {
			break
		}
		buf.Write(response[:n])
	}

	data := buf.String()
	if len(strings.TrimSpace(data)) == 0 {
		return "", fmt.Errorf("no data received from server: %s", err)
	}

	return data, nil
}

func parseServerData(data string) (*CoD4ServerStatus, error) {
	lines := strings.Split(data, "\n")

	if len(lines) < 2 {
		return nil, fmt.Errorf("insufficient data received")
	}

	c := &CoD4ServerStatus{
		raw:        data,
		serverData: make(map[string]string),
		meta:       make(map[string]interface{}),
	}

	tempPlayers := lines[2:]
	tempData := strings.Split(lines[1], "\\")

	for i := 1; i < len(tempData)-1; i += 2 {
		c.serverData[tempData[i]] = tempData[i+1]
	}

	c.serverData["sv_hostname"] = colorCode(c.serverData["sv_hostname"])
	c.serverData["_Maps"] = strings.Join(strings.Split(c.serverData["_Maps"], "-"), ",")
	c.MapName = c.serverData["mapname"]

	startTime, err := time.ParseInLocation(timeLayout, c.serverData["g_mapStartTime"], time.Local)
	if err != nil {
		return nil, err
	}

	c.MapStartTime = startTime

	for _, playerLine := range tempPlayers {
		if len(strings.TrimSpace(playerLine)) > 1 {
			temp := strings.Fields(playerLine)
			if len(temp) >= 3 {
				playerName := strings.Trim(playerLine, `"`)
				scoreValue, err := strconv.Atoi(temp[0])
				if err != nil {
					return nil, err
				}

				pingValue, err := strconv.Atoi(temp[1])
				if err != nil {
					return nil, err
				}

				c.Score = append(c.Score, Score{
					Name:  playerName[strings.Index(playerName, "\"")+1:],
					Score: scoreValue,
					Ping:  pingValue,
				})
			}
		}
	}

	return c, nil
}

func colorCode(str string) string {
	str += "^"

	colorMap := map[string]string{
		"0": "#000000",
		"1": "#F65A5A",
		"2": "#00F100",
		"3": "#EFEE04",
		"4": "#0F04E8",
		"5": "#04E8E7",
		"6": "#F75AF6",
		"7": "#FFFFFF",
		"8": "#7E7E7E",
		"9": "#6E3C3C",
	}

	re := regexp.MustCompile(`\^(\d)(.*?)\^`)
	return re.ReplaceAllStringFunc(str, func(m string) string {
		matches := re.FindStringSubmatch(m)
		return fmt.Sprintf(`<span style="color:%s;">%s</span>^`, colorMap[matches[1]], matches[2])
	})
}
