package app

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var (
	joinPattern          = regexp.MustCompile(`OnPlayerJoined\s+(.+?)\s+\(usr_[0-9a-fA-F-]+\)`)
	leftPattern          = regexp.MustCompile(`OnPlayerLeft\s+(.+?)\s+\(usr_[0-9a-fA-F-]+\)`)
	worldIdPattern       = regexp.MustCompile(`worldId=(wrld_[0-9a-fA-F-]+)`)
	instanceIdPattern    = regexp.MustCompile(`instanceId=([^,}\s]+)`)
	worldNamePattern     = regexp.MustCompile(`worldName=([^,}]+)`)
	worldLocationPattern = regexp.MustCompile(`worldId=(wrld_[0-9a-fA-F-]+):([^\s,\]\)\"']+)`)
	worldPattern         = regexp.MustCompile(`(wrld_[0-9a-fA-F-]+(?::[^\s\]\)\"']+)?)`)
	enteringRoomPattern  = regexp.MustCompile(`\[Behaviour\]\s+(?:Entering Room|Joining or Creating Room):\s+(.+)$`)
)

type vrcLogState struct {
	location         string
	worldID          string
	instanceID       string
	worldName        string
	pendingWorldName string
	presentSet       map[string]struct{}
	initialized      bool
}

func getVrchatLogDir() (string, error) {
	userProfile := os.Getenv("USERPROFILE")
	if userProfile == "" {
		return "", os.ErrNotExist
	}
	return filepath.Join(userProfile, "AppData", "LocalLow", "VRChat", "VRChat"), nil
}

func findLatestVrchatLog() (string, error) {
	dir, err := getVrchatLogDir()
	if err != nil {
		return "", err
	}

	entries, err := os.ReadDir(dir)
	if err != nil {
		return "", err
	}

	var latest string
	var latestMod time.Time
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		if !strings.HasSuffix(strings.ToLower(name), ".log") && !strings.HasSuffix(strings.ToLower(name), ".txt") {
			continue
		}
		info, err := entry.Info()
		if err != nil {
			continue
		}
		if info.ModTime().After(latestMod) {
			latestMod = info.ModTime()
			latest = filepath.Join(dir, name)
		}
	}
	if latest == "" {
		return "", os.ErrNotExist
	}
	return latest, nil
}

func watchVrchatLog() {
	lastPath := ""
	lastSize := int64(0)
	state := &vrcLogState{
		presentSet: map[string]struct{}{},
	}
	for {
		path, err := findLatestVrchatLog()
		if err != nil {
			time.Sleep(2 * time.Second)
			continue
		}
		info, err := os.Stat(path)
		if err != nil {
			time.Sleep(2 * time.Second)
			continue
		}
		if path != lastPath {
			log.Printf("vrchat log watching %s", path)
			lastPath = path
			lastSize = 0
			if err := scanExistingVrchatLog(path, state); err != nil {
				log.Printf("vrchat log initial scan failed: %v", err)
			}
			if info2, err := os.Stat(path); err == nil {
				lastSize = info2.Size()
			} else {
				lastSize = info.Size()
			}
			time.Sleep(2 * time.Second)
			continue
		}

		if info.Size() < lastSize {
			lastSize = 0
		}
		if info.Size() > lastSize {
			f, err := os.Open(path)
			if err != nil {
				time.Sleep(2 * time.Second)
				continue
			}
			_, _ = f.Seek(lastSize, 0)
			scanner := bufio.NewScanner(f)
			for scanner.Scan() {
				line := scanner.Text()
				if err := appendRuntimeLog("VRC LOG", line); err != nil {
					log.Printf("append runtime log failed: %v", err)
				}
				if changed, worldLabel := updateWorldState(state, line); changed {
					log.Printf("world changed: %s", worldLabel)
					state.presentSet = map[string]struct{}{}
					SetCurrentWorld(worldLabel)
					if tracker := GetGuestTracker(); tracker != nil {
						tracker.SetCurrentInstance(worldLabel)
					}
					_ = appendRuntimeLog("VRC WORLD", worldLabel)
				}
				at := extractLineTime(line)
				if m := joinPattern.FindStringSubmatch(line); len(m) == 2 {
					name := strings.TrimSpace(m[1])
					state.presentSet[name] = struct{}{}
					if tracker := GetGuestTracker(); tracker != nil {
						tracker.MarkJoin(name, at)
					}
					out := fmt.Sprintf("[join] %s (%d)", name, len(state.presentSet))
					log.Print(out)
					_ = appendJoinLeaveLog(out)
					continue
				}
				if m := leftPattern.FindStringSubmatch(line); len(m) == 2 {
					name := strings.TrimSpace(m[1])
					delete(state.presentSet, name)
					if tracker := GetGuestTracker(); tracker != nil {
						tracker.MarkLeave(name, at)
					}
					out := fmt.Sprintf("[leave] %s (%d)", name, len(state.presentSet))
					log.Print(out)
					_ = appendJoinLeaveLog(out)
					continue
				}
			}
			lastSize = info.Size()
			_ = f.Close()
		}
		time.Sleep(2 * time.Second)
	}
}

func scanExistingVrchatLog(path string, state *vrcLogState) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	state.presentSet = map[string]struct{}{}
	state.location = ""
	state.worldID = ""
	state.instanceID = ""
	state.worldName = ""
	state.pendingWorldName = ""
	state.initialized = false

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		if changed, _ := updateWorldState(state, line); changed {
			state.presentSet = map[string]struct{}{}
		}
		if m := joinPattern.FindStringSubmatch(line); len(m) == 2 {
			name := strings.TrimSpace(m[1])
			state.presentSet[name] = struct{}{}
			if tracker := GetGuestTracker(); tracker != nil {
				tracker.MarkJoin(name, extractLineTime(line))
			}
			continue
		}
		if m := leftPattern.FindStringSubmatch(line); len(m) == 2 {
			name := strings.TrimSpace(m[1])
			delete(state.presentSet, name)
			if tracker := GetGuestTracker(); tracker != nil {
				tracker.MarkLeave(name, extractLineTime(line))
			}
			continue
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}
	state.initialized = true
	if state.worldName != "" {
		log.Printf("world changed: %s", state.worldName)
		SetCurrentWorld(state.worldName)
		if tracker := GetGuestTracker(); tracker != nil {
			tracker.SetCurrentInstance(state.worldName)
		}
		_ = appendRuntimeLog("VRC WORLD", state.worldName)
	}
	return nil
}

func updateWorldState(state *vrcLogState, line string) (bool, string) {
	worldID := ""
	instanceID := ""
	worldName := ""
	roomTitle := ""

	if m := enteringRoomPattern.FindStringSubmatch(line); len(m) == 2 {
		roomTitle = strings.TrimSpace(m[1])
		state.pendingWorldName = roomTitle
	}
	if m := worldLocationPattern.FindStringSubmatch(line); len(m) == 3 {
		worldID = strings.TrimSpace(m[1])
		instanceID = strings.TrimSpace(m[2])
	}

	if m := worldIdPattern.FindStringSubmatch(line); len(m) == 2 {
		worldID = strings.TrimSpace(m[1])
	}
	if m := instanceIdPattern.FindStringSubmatch(line); len(m) == 2 {
		instanceID = strings.TrimSpace(m[1])
	}
	if m := worldNamePattern.FindStringSubmatch(line); len(m) == 2 {
		worldName = strings.TrimSpace(m[1])
	}

	if m := enteringRoomPattern.FindStringSubmatch(line); len(m) == 2 && worldID == "" {
		if worldMatch := worldPattern.FindStringSubmatch(m[1]); len(worldMatch) == 2 {
			worldID = worldMatch[1]
		}
	}

	nextLocation := ""
	if worldID != "" {
		nextLocation = worldID
		if instanceID != "" {
			nextLocation = worldID + ":" + instanceID
		}
	}

	if nextLocation != "" && nextLocation != state.location {
		state.location = nextLocation
		state.worldID = worldID
		state.instanceID = instanceID
		if worldName != "" {
			state.worldName = worldName
		} else if shouldUseRoomTitlePreferLatest(roomTitle, state.worldName) {
			state.worldName = roomTitle
		}
		label := state.worldName
		if label == "" {
			label = state.worldID
		}
		if label == "" {
			label = nextLocation
		}
		if state.initialized {
			log.Printf("world changed: %s", label)
			_ = appendRuntimeLog("VRC WORLD", label)
		}
		return true, label
	}

	if worldName != "" {
		state.worldName = worldName
		state.pendingWorldName = ""
		return false, ""
	}
	if shouldUseRoomTitlePreferLatest(roomTitle, state.worldName) {
		state.worldName = roomTitle
	}

	return false, ""
}

func shouldUseRoomTitle(roomTitle, currentWorldName string) bool {
	if roomTitle == "" {
		return false
	}
	if strings.Contains(roomTitle, "Home Location") {
		return false
	}
	if roomTitle == "Holiday-Cottage" && currentWorldName != "" && currentWorldName != roomTitle {
		return false
	}
	if currentWorldName == "" {
		return true
	}
	if roomTitle == currentWorldName {
		return false
	}
	if len(roomTitle) > len(currentWorldName) {
		return true
	}
	if strings.Contains(roomTitle, "「") || strings.Contains(roomTitle, "」") || strings.Contains(roomTitle, "[") {
		return true
	}
	return false
}

func shouldUseRoomTitlePreferLatest(roomTitle, currentWorldName string) bool {
	if roomTitle == "" {
		return false
	}
	if strings.Contains(roomTitle, "Home Location") {
		return false
	}
	if currentWorldName == "" {
		return true
	}
	if roomTitle == currentWorldName {
		return false
	}
	return true
}

func extractLineTime(line string) time.Time {
	if len(line) < 19 {
		return time.Now()
	}
	t, err := time.ParseInLocation("2006.01.02 15:04:05", line[:19], time.Local)
	if err == nil {
		return t
	}
	return time.Now()
}
