package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"

	_ "embed"

	logger "git.site.quack-lab.dev/dave/cylogger"
	"github.com/bmatcuk/doublestar/v4"
	lua "github.com/yuin/gopher-lua"
)

const nsqEndpoint = "https://nsq.site.quack-lab.dev/pub?topic=wowspy"

var debug *bool
var messageQueue = make(chan string, 10000)
var nsqWorkers = 32

func main() {
	root := flag.String("root", ".", "Root workdir")
	debug = flag.Bool("d", false, "Debug")
	flag.Parse()
	logger.InitFlag()

	logger.Info("Root: %q", *root)
	cleanedRoot := strings.Replace(*root, "~", os.Getenv("HOME"), 1)
	cleanedRoot, err := filepath.Abs(cleanedRoot)
	if err != nil {
		logger.Error("error getting absolute path: %v", err)
		return
	}
	cleanedRoot = filepath.Clean(cleanedRoot)
	cleanedRoot = strings.TrimSuffix(cleanedRoot, "/")

	logger.Info("Looking for Heimdall.lua in %q", cleanedRoot)
	matches, err := doublestar.Glob(os.DirFS(cleanedRoot), "**/Heimdall.lua")
	if err != nil {
		logger.Error("error matching Heimdall.lua: %v", err)
		return
	}
	logger.Info("Found %d Heimdall.lua", len(matches))

	wg := sync.WaitGroup{}
	messages := make(chan NSQMessage, 1000)
	for i := 0; i < nsqWorkers; i++ {
		wg.Add(1)
		go NsqWorker(&wg, messages)
	}

	for _, match := range matches {
		logger.Info("Processing %q", match)
		wg.Add(1)
		go ParseHeimdallFile(filepath.Join(cleanedRoot, match), &wg, messages)
	}
	for msg := range messages {
		logger.Info("Message: %#v", msg)
	}
	wg.Wait()
	close(messages)
	return
}

func ParseHeimdallFile(path string, wg *sync.WaitGroup, messages chan NSQMessage) {
	defer wg.Done()
	L := lua.NewState()
	defer L.Close()

	if err := L.DoFile(path); err != nil {
		logger.Error("error executing Lua file %q: %v", path, err)
		return
	}

	heimdallAchievements := L.GetGlobal("Heimdall_Achievements")
	if heimdallAchievements.Type() == lua.LTNil {
		logger.Error("Heimdall_Achievements not found in %q", path)
		return
	}

	playersTableLua := L.GetField(heimdallAchievements, "players")
	playersTable, ok := playersTableLua.(*lua.LTable)
	if !ok || playersTableLua.Type() == lua.LTNil {
		logger.Error("'players' table not found or not a table in Heimdall_Achievements in %q", path)
		return
	}

	playersTable.ForEach(func(playerNameLua lua.LValue, playerAchievementsLua lua.LValue) {
		currentPlayerName := playerNameLua.String()
		logger.Info("Processing player: %s in %q", currentPlayerName, path)

		achievementsTable, ok := playerAchievementsLua.(*lua.LTable)
		if !ok {
			logger.Error("Achievements for player %s is not a table in %q", currentPlayerName, path)
			return
		}

		achievementsTable.ForEach(func(_ lua.LValue, achievementDataLua lua.LValue) {
			achievementTable, ok := achievementDataLua.(*lua.LTable)
			if !ok {
				logger.Error("Achievement data for player %s is not a table in %q", currentPlayerName, path)
				return
			}

			currentAchievement := NSQMessage{Name: currentPlayerName}

			idVal := achievementTable.RawGetString("id")
			if idVal.Type() == lua.LTNumber {
				currentAchievement.ID = lua.LVAsString(idVal)
			} else if idVal.Type() == lua.LTString {
				currentAchievement.ID = idVal.String()
			} else {
				logger.Warning("Missing or invalid 'id' (expected number or string) for achievement for player %s in %q", currentPlayerName, path)
			}

			dateVal := achievementTable.RawGetString("date")
			if dateVal.Type() == lua.LTString {
				currentAchievement.Date = dateVal.String()
			} else {
				logger.Warning("Missing or invalid 'date' (expected string) for achievement for player %s in %q", currentPlayerName, path)
			}

			completedVal := achievementTable.RawGetString("completed")
			if completedVal.Type() == lua.LTBool {
				currentAchievement.Completed = lua.LVAsBool(completedVal)
			} else {
				logger.Warning("Missing or invalid 'completed' (expected boolean) for achievement for player %s in %q", currentPlayerName, path)
			}

			if currentAchievement.ID != "" { // Ensure we have at least an ID before sending
				logger.Info("Publishing achievement for %s: ID %s, Date: %s, Completed: %t", currentPlayerName, currentAchievement.ID, currentAchievement.Date, currentAchievement.Completed)
				messages <- currentAchievement
			}
		})
	})
}

func NsqWorker(wg *sync.WaitGroup, messages chan NSQMessage) {
	defer wg.Done()
	for msg := range messages {
		err := Publish(msg)
		if err != nil {
			logger.Warning("error publishing message: %v", err)
			continue
		}
	}
}

func Publish(msg NSQMessage) error {
	data := bytes.Buffer{}
	err := json.NewEncoder(&data).Encode(msg)
	if err != nil {
		return err
	}
	_, err = http.Post(nsqEndpoint, "application/json", &data)
	if err != nil {
		return err
	}
	return nil
}
