package minecraft

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

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

/*
   Smart verification + self-repair for Minecraft installs.

   Layout: /opt/zlh/<game>/<variant>/world

   - Ensures Java symlink exists and is executable
   - For Vanilla-like variants: verifies ONLY server.jar  (NO start.sh)
   - For Forge/NeoForge: verifies run.sh, user_jvm_args.txt, libraries/, patched scripts
   - Automatic correction on failure (2 attempts)
*/

func VerifyMinecraftInstallWithRepair(cfg state.Config) error {
	const maxAttempts = 2

	var lastErr error

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		if attempt > 1 {
			fmt.Println("[verify] Attempt", attempt, "after correction")
		}

		if err := verifyMinecraftInstallOnce(cfg); err != nil {
			lastErr = err
			fmt.Println("[verify] Install verification failed:", err)

			// Try to correct issues before retrying
			if attempt < maxAttempts {
				if corrErr := correctMinecraftInstall(cfg); corrErr != nil {
					fmt.Println("[verify] Correction step failed:", corrErr)
					break
				}
				continue
			}

			break
		}

		fmt.Println("[verify] Minecraft installation verified OK")
		return nil
	}

	if lastErr == nil {
		lastErr = fmt.Errorf("minecraft installation invalid (unknown reason)")
	}
	return lastErr
}

// -----------------------------------------------------------------------------
// Single verification attempt (no repair)
// -----------------------------------------------------------------------------

func verifyMinecraftInstallOnce(cfg state.Config) error {
	dir := provcommon.ServerDir(cfg)
	variant := strings.ToLower(cfg.Variant)
	runtime := strings.ToLower(strings.TrimSpace(cfg.Runtime))
	if runtime == "" {
		runtime = variant
	}
	fmt.Printf("[verify] runtime=%s\n", runtime)

	// Java symlink must exist
	if err := verifyJavaSymlink(); err != nil {
		return err
	}

	if runtime == "fabric" {
		if err := verifyServerJar(cfg, dir); err != nil {
			return err
		}
		return nil
	}

	// ---------------------------------------------------------------------
	// VANILLA / PAPER / PURPUR / FABRIC / QUILT → *ONLY REQUIRE server.jar*
	// ---------------------------------------------------------------------
	if variant == "vanilla" ||
		variant == "paper" ||
		variant == "purpur" ||
		variant == "fabric" ||
		variant == "quilt" {

		if err := verifyServerJar(cfg, dir); err != nil {
			return err
		}

		// ❌ Removed verifyStartScript(dir)
		// Vanilla-like variants DO NOT use start.sh

		return nil
	}

	// ---------------------------------------------------------------------
	// FORGE / NEOFORGE → require run.sh + libs + JVM args
	// ---------------------------------------------------------------------
	if variant == "forge" || variant == "neoforge" {
		if err := verifyForgeLayout(dir); err != nil {
			return err
		}
		return nil
	}

	return fmt.Errorf("unknown minecraft variant: %s", variant)
}

/* --------------------------------------------------------------------------
   Java symlink check
----------------------------------------------------------------------------*/

func verifyJavaSymlink() error {
	linkPath := filepath.Join(provcommon.JavaRoot, "java")

	info, err := os.Lstat(linkPath)
	if err != nil {
		return fmt.Errorf("java symlink missing at %s: %w", linkPath, err)
	}
	if info.Mode()&os.ModeSymlink == 0 {
		return fmt.Errorf("expected java symlink at %s, found non-symlink", linkPath)
	}

	target, err := os.Readlink(linkPath)
	if err != nil {
		return fmt.Errorf("readlink java symlink: %w", err)
	}

	if !filepath.IsAbs(target) {
		target = filepath.Join(filepath.Dir(linkPath), target)
	}

	if tInfo, err := os.Stat(target); err != nil || tInfo.IsDir() {
		return fmt.Errorf("java symlink invalid: %s", target)
	}

	return nil
}

// -----------------------------------------------------------------------------
// Vanilla-like variants: require ONLY server.jar
// -----------------------------------------------------------------------------

func verifyServerJar(cfg state.Config, dir string) error {
	jarName := "server.jar"
	jar := filepath.Join(dir, jarName)
	if _, err := os.Stat(jar); err != nil {
		return fmt.Errorf("%s missing in %s: %w", jarName, dir, err)
	}
	fmt.Printf("[verify] found=%s\n", jarName)
	return nil
}

// -----------------------------------------------------------------------------
// Forge / NeoForge layout validation
// -----------------------------------------------------------------------------

func verifyForgeLayout(dir string) error {
	runSh := filepath.Join(dir, "run.sh")
	info, err := os.Stat(runSh)
	if err != nil {
		return fmt.Errorf("forge requires run.sh but it is missing: %w", err)
	}
	if info.Mode()&0o111 == 0 {
		return fmt.Errorf("run.sh is not executable (%s)", runSh)
	}

	userArgs := filepath.Join(dir, "user_jvm_args.txt")
	if _, err := os.Stat(userArgs); err != nil {
		return fmt.Errorf("forge user_jvm_args.txt missing: %w", err)
	}

	libs := filepath.Join(dir, "libraries")
	if s, err := os.Stat(libs); err != nil || !s.IsDir() {
		return fmt.Errorf("forge libraries folder missing or not a directory: %w", err)
	}

	if err := verifyJavaPatchedInScripts(dir); err != nil {
		return err
	}

	return nil
}

func verifyJavaPatchedInScripts(dir string) error {
	runSh := filepath.Join(dir, "run.sh")
	data, err := os.ReadFile(runSh)
	if err != nil {
		return fmt.Errorf("read run.sh failed: %w", err)
	}

	if !strings.Contains(string(data), "/opt/zlh/runtime/java") {
		return fmt.Errorf("run.sh does not reference /opt/zlh/runtime/java")
	}

	return nil
}

/* --------------------------------------------------------------------------
   Automatic correction logic
----------------------------------------------------------------------------*/

func correctMinecraftInstall(cfg state.Config) error {
	dir := provcommon.ServerDir(cfg)

	fmt.Println("[verify] Attempting automatic correction of Minecraft install")

	if err := ensureJavaSymlink(cfg); err != nil {
		fmt.Println("[verify] ensureJavaSymlink failed:", err)
	}

	if err := ensureScriptsPatched(dir); err != nil {
		fmt.Println("[verify] ensureScriptsPatched failed:", err)
	}

	// If start.sh exists (forge), ensure executable
	script := filepath.Join(dir, "start.sh")
	if info, err := os.Stat(script); err == nil {
		if info.Mode()&0o111 == 0 {
			_ = os.Chmod(script, info.Mode()|0o755)
			fmt.Println("[verify] made start.sh executable")
		}
	}

	return nil
}

func ensureJavaSymlink(cfg state.Config) error {
	if err := verifyJavaSymlink(); err == nil {
		return nil
	}
	return fmt.Errorf("java runtime invalid or missing")
}

func ensureScriptsPatched(dir string) error {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return err
	}

	for _, e := range entries {
		if e.IsDir() {
			continue
		}

		name := e.Name()
		if !strings.HasSuffix(name, ".sh") &&
			!strings.HasSuffix(name, ".txt") &&
			!strings.HasSuffix(name, ".args") {
			continue
		}

		path := filepath.Join(dir, name)
		data, err := os.ReadFile(path)
		if err != nil {
			continue
		}

		content := string(data)
		patched := strings.ReplaceAll(content, "java ", "/opt/zlh/runtime/java ")

		if patched != content {
			fmt.Println("[verify] patching java call in", name)
			_ = os.WriteFile(path, []byte(patched), 0o755)
		}
	}

	return nil
}
