package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/fsnotify/fsnotify"
	"github.com/joho/godotenv"
)

type ProfileData struct {
	Owner           string    `json:"owner"`
	Timestamp       time.Time `json:"timestamp"`
	Rank            int       `json:"rank"`
	Upload          string    `json:"upload"`
	CurrentUpload   string    `json:"current_upload"`
	CurrentDownload string    `json:"current_download"`
	Points          int       `json:"points"`
	SeedingCount    int       `json:"seeding_count"`
}

var (
	profiles     = map[string]string{}
	jsonFile     = "./data/data.json"
	profilesFile = "./data/profiles.json"
	baseUrl      = "https://ncore.pro/profile.php?id="
	nick         string
	pass         string
	client       *http.Client
)

func init() {
	_ = godotenv.Load(".env.local")
	godotenv.Load()

	nick = os.Getenv("NICK")
	pass = os.Getenv("PASS")

	if _, err := os.Stat(profilesFile); os.IsNotExist(err) {
		log.Printf("File %s does not exist, creating an empty one", profilesFile)
		err := os.WriteFile(profilesFile, []byte("{}"), 0644)
		if err != nil {
			log.Fatalf("Failed to create profiles file: %v", err)
		}
	}

	loadProfiles()

	client = &http.Client{}
}

func loadProfiles() {
	file, err := os.Open(profilesFile)
	if err != nil {
		log.Fatalf("Failed to open profiles file: %v", err)
	}
	defer file.Close()

	jsonBytes, err := io.ReadAll(file)
	if err != nil {
		log.Fatalf("Failed to read profiles file: %v", err)
	}

	var tempProfiles map[string]string
	err = json.Unmarshal(jsonBytes, &tempProfiles)
	if err != nil {
		log.Fatalf("Failed to unmarshal profiles JSON: %v", err)
	}

	for k, v := range tempProfiles {
		profiles[k] = baseUrl + v
	}
	log.Println("Profiles loaded successfully.")
}

func fetchProfile(url string, displayName string) (*ProfileData, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("error creating request for %s: %v", displayName, err)
	}

	req.Header.Set("Cookie", fmt.Sprintf("nick=%s; pass=%s", nick, pass))

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error fetching profile for %s: %v", displayName, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to fetch profile %s: received status %d", displayName, resp.StatusCode)
	}

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error parsing profile document for %s: %v", displayName, err)
	}

	profile := &ProfileData{
		Owner:     displayName,
		Timestamp: time.Now(),
	}

	doc.Find(".userbox_tartalom_mini .profil_jobb_elso2").Each(func(i int, s *goquery.Selection) {
		label := s.Text()
		value := s.Next().Text()

		switch label {
		case "Helyezés:":
			value = strings.TrimSuffix(value, ".")
			rank, err := strconv.Atoi(value)
			if err == nil {
				profile.Rank = rank
			}
		case "Feltöltés:":
			profile.Upload = value
		case "Aktuális feltöltés:":
			profile.CurrentUpload = value
		case "Aktuális letöltés:":
			profile.CurrentDownload = value
		case "Pontok száma:":
			points, err := strconv.Atoi(value)
			if err == nil {
				profile.Points = points
			}
		}
	})

	doc.Find(".lista_mini_fej").Each(func(i int, s *goquery.Selection) {
		text := s.Text()
		re := regexp.MustCompile(`\((\d+)\)`)
		matches := re.FindStringSubmatch(text)
		if len(matches) > 1 {
			fmt.Sscanf(matches[1], "%d", &profile.SeedingCount)
		}
	})

	return profile, nil
}

func readExistingProfiles() ([]ProfileData, error) {
	if _, err := os.Stat(jsonFile); os.IsNotExist(err) {
		log.Printf("File %s does not exist, returning an empty profile list.", jsonFile)
		return []ProfileData{}, nil
	}

	file, err := os.Open(jsonFile)
	if err != nil {
		return nil, fmt.Errorf("error opening %s: %v", jsonFile, err)
	}
	defer file.Close()

	var existingProfiles []ProfileData
	byteValue, err := io.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("error reading %s: %v", jsonFile, err)
	}

	err = json.Unmarshal(byteValue, &existingProfiles)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling profile data: %v", err)
	}
	return existingProfiles, nil
}

func logToJSON(profile *ProfileData) error {
	existingProfiles, err := readExistingProfiles()
	if err != nil {
		return err
	}

	existingProfiles = append(existingProfiles, *profile)

	file, err := os.OpenFile(jsonFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("error opening file for writing: %v", err)
	}
	defer file.Close()

	enc := json.NewEncoder(file)
	enc.SetIndent("", "  ")
	err = enc.Encode(existingProfiles)
	if err != nil {
		return fmt.Errorf("error encoding JSON data: %v", err)
	}
	log.Printf("Profile for %s logged successfully.", profile.Owner)
	return nil
}

func dataHandler(w http.ResponseWriter, r *http.Request) {
	existingProfiles, err := readExistingProfiles()
	if err != nil {
		http.Error(w, "Could not read data", http.StatusInternalServerError)
		log.Printf("Error reading profiles: %v", err)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	err = json.NewEncoder(w).Encode(existingProfiles)
	if err != nil {
		log.Printf("Error encoding profiles to JSON: %v", err)
	}
}

func serveHTML(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "index.html")
}

func watchProfilesFile() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatalf("Error creating file watcher: %v", err)
	}
	defer watcher.Close()

	err = watcher.Add(profilesFile)
	if err != nil {
		log.Fatalf("Error adding file to watcher: %v", err)
	}

	debounce := time.AfterFunc(0, func() {})

	for {
		select {
		case event, ok := <-watcher.Events:
			if !ok {
				return
			}
			if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
				log.Println("Profiles file changed, reloading profiles...")
				debounce.Stop() // Stop any running debounce timer
				debounce = time.AfterFunc(500*time.Millisecond, func() {
					loadProfiles()
					for displayName, url := range profiles {
						profile, err := fetchProfile(url, displayName)
						if err != nil {
							log.Printf("Error fetching profile for %s after file change: %v", displayName, err)
							continue
						}

						if err := logToJSON(profile); err != nil {
							log.Printf("Error logging profile for %s: %v", displayName, err)
						}
					}
				})
			}
		case err, ok := <-watcher.Errors:
			if !ok {
				return
			}
			log.Printf("File watcher error: %v", err)
		}
	}
}

func main() {
	ticker := time.NewTicker(time.Hour * 24)
	defer ticker.Stop()

	go func() {
		for {
			for displayName, url := range profiles {
				profile, err := fetchProfile(url, displayName)
				if err != nil {
					log.Printf("Error fetching profile %s: %v", displayName, err)
					continue
				}

				if err := logToJSON(profile); err != nil {
					log.Printf("Error logging profile for %s: %v", displayName, err)
				}
			}
			<-ticker.C
		}
	}()

	go watchProfilesFile()

	http.HandleFunc("/data", dataHandler)
	http.HandleFunc("/", serveHTML)
	log.Println("Server is starting on port 3000...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}