package minecraft

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"zlh-agent/internal/provision"
	"zlh-agent/internal/state"
)

func WaitUntilReady(cfg state.Config, timeout, interval time.Duration) error {
	start := time.Now()
	ports := candidatePorts(cfg)
	protocols := []int{ProtocolForVersion(cfg.Version), 767, 765, 763, 762, 754}
	protocols = dedupeInts(protocols)

	attempt := 0
	deadline := start.Add(timeout)
	var lastErr error

	for {
		attempt++
		for _, port := range ports {
			for _, protocol := range protocols {
				if _, err := QueryStatus("127.0.0.1", port, protocol); err == nil {
					elapsed := time.Since(start).Milliseconds()
					log.Printf("[lifecycle] vmid=%d phase=probe result=ready attempt=%d elapsed_ms=%d port=%d protocol=%d", cfg.VMID, attempt, elapsed, port, protocol)
					return nil
				} else {
					lastErr = err
				}
			}
		}

		elapsed := time.Since(start).Milliseconds()
		log.Printf("[lifecycle] vmid=%d phase=probe result=not_ready attempt=%d elapsed_ms=%d err=%v", cfg.VMID, attempt, elapsed, lastErr)

		if time.Now().After(deadline) {
			if lastErr != nil {
				return fmt.Errorf("minecraft readiness probe timeout after %s: %w", timeout, lastErr)
			}
			return fmt.Errorf("minecraft readiness probe timeout after %s", timeout)
		}
		time.Sleep(interval)
	}
}

func candidatePorts(cfg state.Config) []int {
	ports := make([]int, 0, 3)
	propsPath := filepath.Join(provision.ServerDir(cfg), "server.properties")
	if b, err := os.ReadFile(propsPath); err == nil {
		lines := strings.Split(string(b), "\n")
		for _, l := range lines {
			if strings.HasPrefix(l, "server-port=") {
				if p, err := strconv.Atoi(strings.TrimPrefix(l, "server-port=")); err == nil && p > 0 {
					ports = append(ports, p)
				}
				break
			}
		}
	}
	if len(cfg.Ports) > 0 && cfg.Ports[0] > 0 {
		ports = append(ports, cfg.Ports[0])
	}
	ports = append(ports, 25565)
	return dedupeInts(ports)
}

func dedupeInts(in []int) []int {
	seen := make(map[int]struct{}, len(in))
	out := make([]int, 0, len(in))
	for _, v := range in {
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}
