package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

var DX = readDXExpeFile("dx.txt")

type ClusterMessage struct {
	From   string
	DX     string
	Freq   string
	Mode   string
	Report string
	Time   string
}

func ParseFlags() (string, error) {
	// String that contains the configured configuration path
	var configPath string

	// Set up a CLI flag called "-config" to allow users
	// to supply the configuration file
	flag.StringVar(&configPath, "config", "./config.yml", "path to config file")

	// Actually parse the flags
	flag.Parse()

	// Validate the path first
	if err := ValidateConfigPath(configPath); err != nil {
		return "", err
	}

	// Return the configuration path
	return configPath, nil
}

// Message structure for Gotify
type GotifyMessage struct {
	Title    string `json:"title"`
	Message  string `json:"message"`
	Priority int    `json:"priority"`
}

func readDXExpeFile(filename string) string {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Println("Error while opening file", err)
	}
	defer file.Close()

	// Lire tout le contenu du fichier
	content, err := io.ReadAll(file)
	if err != nil {
		fmt.Println("Error while reading the file", err)
	}
	log.Printf("DX Expe file has been loaded properly with following calls: %s", content)
	return string(content)
}

// Function to send message to Gotify
func sendToGotify(title string, sMess ClusterMessage, priority int, cfg Config) {

	message := fmt.Sprintf("DX: %s\nFrom: %s\nFreq: %s\nMode: %s\nReport: %s\nTime: %s", sMess.DX, sMess.From, sMess.Freq, sMess.Mode, sMess.Report, sMess.Time)

	gotifyMsg := GotifyMessage{
		Title:    title,
		Message:  message,
		Priority: priority,
	}

	jsonData, err := json.Marshal(gotifyMsg)
	if err != nil {
		log.Println("Error marshaling JSON:", err)
		return
	}

	req, err := http.NewRequest("POST", cfg.Gotify.URL, bytes.NewBuffer(jsonData))
	if err != nil {
		log.Println("Error creating request:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Add("Authorization", "Bearer "+cfg.Gotify.Token)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Error sending request:", err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Println("Gotify server returned non-OK status:", resp.Status)
	} else {
		log.Println("message successfully sent to Gotify")
	}

}

func SanitizeClusterMessage(message string) ClusterMessage {
	r := regexp.MustCompile(`DX\sde\s([A-Z0-9]+)[-#:]+[\s]+([0-9]+.[0-9])[\s]+([^\s]+)[\s]+([A-Z]+[0-9])\s+(.*dB).*(.{4})Z$`)
	matches := r.FindStringSubmatch(message)

	mes := ClusterMessage{}

	if len(matches) > 0 {
		timeLayout := "1504"
		formatedTime, _ := time.Parse(timeLayout, matches[6])
		formatedTime = formatedTime.Add(time.Hour * time.Duration(7))
		newTime := formatedTime.Format("15:04")

		mes := ClusterMessage{
			From:   matches[1],
			Freq:   matches[2],
			DX:     matches[3],
			Mode:   matches[4],
			Report: matches[5],
			Time:   newTime,
		}

		return mes
	}
	return mes
}

func sendTelnetMessage(conn net.Conn, message string) {
	if !strings.HasPrefix(message, "F4BPO") {
		time.Sleep(2 * time.Second)
	}
	_, err := conn.Write([]byte(message + "\n"))
	if err != nil {
		conn.Close()
		time.Sleep(5 * time.Second) // Wait before retrying
	}
}

func sendFilters(conn net.Conn) {
	go sendTelnetMessage(conn, "set/ft8")
	time.Sleep(1 * time.Second)
	go sendTelnetMessage(conn, "SET/FILTER DOC/PASS 3W")
	time.Sleep(1 * time.Second)
	go sendTelnetMessage(conn, "set/skimmer")
	time.Sleep(1 * time.Second)
	go sendTelnetMessage(conn, "set/ft4")
}

func main() {

	// Generate our config based on the config supplied
	// by the user in the flags
	cfgPath, err := ParseFlags()
	if err != nil {
		log.Fatal(err)
	}
	cfg, err := NewConfig(cfgPath)
	if err != nil {
		log.Fatal(err)
	}

	login := false
	filters_sent := false

	fmt.Println("PushDXCluster v0.1")
	for {
		// Connect to the Telnet server
		conn, err := net.Dial("tcp", cfg.Cluster.Host)
		if err != nil {
			log.Printf("Failed to connect to Telnet server: %v", err)
			time.Sleep(5 * time.Second) // Wait before retrying
			continue
		}

		log.Printf("Connected to %s server", cfg.Cluster.Host)

		// Create a buffered reader to read from the Telnet server
		reader := bufio.NewReader(conn)

		// Loop to read from the Telnet server
		for {
			message, err := reader.ReadString('\n')
			if err != nil {
				log.Printf("Error reading from Telnet server: %v", err)
				conn.Close()
				break
			}

			// Trim and Sanitize spots messages
			message = strings.TrimSpace(message)
			sMess := SanitizeClusterMessage(message)

			if sMess.DX != "" {
				log.Printf("Sanitized message: Reporter: %s, DX: %s, Freq: %s, Report: %s, Time: %s", sMess.From, sMess.DX, sMess.Freq, sMess.Report, sMess.Time)
			} else {
				log.Printf("Received message: %s", message)
			}

			// Send Call to ID to the Server
			if message != "" && strings.Contains("Please enter your call:", message) {
				go sendTelnetMessage(conn, "F4BPO-2")
				login = true
			}

			if message != "" && strings.Contains("login:", message) {
				go sendTelnetMessage(conn, "F4BPO-2")
				login = true
			}

			// Set the FT8, CW, FT4 and only spots from Vietnam filters only once
			if login && !filters_sent {
				go sendFilters(conn)
				filters_sent = true
			}

			// If calls is in the DX List then send a Gotify notification
			if sMess.DX != "" && sMess.From == "F4BPO" && strings.Contains(DX, sMess.DX) {
				sendToGotify("Spot", sMess, 5, *cfg)
			}
		}

		log.Println("Disconnected from Telnet server, reconnecting...")
		time.Sleep(5 * time.Second) // Wait before reconnecting
	}
}