package handlers

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

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

func modsLogf(cfg *state.Config, format string, args ...any) {
	log.Printf("[mods] vmid=%d type=%s game=%s variant=%s "+format, append([]any{cfg.VMID, cfg.ContainerType, cfg.Game, cfg.Variant}, args...)...)
}

type jsonError struct {
	Error string `json:"error"`
}

func HandleGameMods(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeJSONError(w, http.StatusMethodNotAllowed, "GET only")
		return
	}

	_, serverRoot, ok := requireMinecraftGame(w)
	if !ok {
		return
	}

	resp, err := mods.ScanMods(serverRoot)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, err.Error())
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

func HandleGameModsInstall(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeJSONError(w, http.StatusMethodNotAllowed, "POST only")
		return
	}
	endOp, ok := beginHandlerOperation(w, "mod_install", false, "installing mod")
	if !ok {
		return
	}
	defer endOp()

	cfg, serverRoot, ok := requireMinecraftGame(w)
	if !ok {
		return
	}
	if isVanillaModLocked(cfg) {
		modsLogf(cfg, "enforcement=vanilla user_mods_blocked=true action=install status=denied")
		writeJSONError(w, http.StatusForbidden, "Vanilla servers do not support user-installed mods")
		return
	}

	var req mods.InstallRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "invalid json")
		return
	}

	filename := strings.TrimSpace(req.Filename)
	if strings.TrimSpace(strings.ToLower(req.Source)) != "modrinth" {
		filename = ""
		if u, err := url.Parse(req.ArtifactURL); err == nil {
			base := filepath.Base(u.Path)
			safeBase := base != "." && base != "/" &&
				!strings.Contains(base, "..") &&
				!strings.Contains(base, "/") &&
				!strings.Contains(base, "\\") &&
				!strings.Contains(base, "~") &&
				!strings.ContainsRune(base, 0)
			if safeBase {
				valid := true
				for _, r := range base {
					if r <= 31 || r == 127 || r == ' ' || r == '\t' || r == '\n' || r == '\r' {
						valid = false
						break
					}
					if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '.' || r == '_' || r == '-' || r == '+') {
						valid = false
						break
					}
				}
				if valid {
					filename = base
				}
			}
		}
		if filename == "" {
			name := strings.TrimSpace(req.ModID)
			if strings.TrimSpace(req.Version) != "" {
				name = name + "-" + strings.TrimSpace(req.Version)
			}
			name = strings.Map(func(r rune) rune {
				if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '.' || r == '_' || r == '-' {
					return r
				}
				return '_'
			}, name)
			if len(name) > 64 {
				name = name[:64]
			}
			if name == "" {
				name = "unknown"
			}
			filename = name + ".jar"
		}
	}

	modsDir := filepath.Join(serverRoot, "mods")
	enabledPath := filepath.Join(modsDir, filename)
	disabledPath := enabledPath + ".disabled"
	if _, err := os.Stat(enabledPath); err == nil {
		modsLogf(cfg, "action=install mod_id=%s source=%s filename=%s status=already_installed enabled=true", req.ModID, req.Source, filename)
		writeJSON(w, http.StatusOK, map[string]any{
			"status":   "already-installed",
			"fileName": filename,
			"enabled":  true,
		})
		return
	}
	if _, err := os.Stat(disabledPath); err == nil {
		modsLogf(cfg, "action=install mod_id=%s source=%s filename=%s status=already_installed enabled=false", req.ModID, req.Source, filename)
		writeJSON(w, http.StatusOK, map[string]any{
			"status":   "already-installed",
			"fileName": filename,
			"enabled":  false,
		})
		return
	}

	resp, err := mods.InstallCurated(serverRoot, req)
	if err != nil {
		modsLogf(cfg, "action=install mod_id=%s source=%s status=failed err=%v", req.ModID, req.Source, err)
		writeJSONError(w, http.StatusBadRequest, err.Error())
		return
	}
	_ = resp
	modsLogf(cfg, "action=install mod_id=%s source=%s filename=%s status=ok", req.ModID, req.Source, filename)
	writeJSON(w, http.StatusOK, map[string]any{
		"status":   "installed",
		"fileName": filename,
	})
}

func HandleGameModByID(w http.ResponseWriter, r *http.Request) {
	cfg, serverRoot, ok := requireMinecraftGame(w)
	if !ok {
		return
	}
	if isVanillaModLocked(cfg) {
		modsLogf(cfg, "enforcement=vanilla user_mods_blocked=true action=%s status=denied", strings.ToLower(r.Method))
		writeJSONError(w, http.StatusForbidden, "Vanilla servers do not support user-installed mods")
		return
	}

	modID := strings.TrimPrefix(r.URL.Path, "/game/mods/")
	if modID == "" || strings.Contains(modID, "/") || !mods.IsValidModID(modID) {
		writeJSONError(w, http.StatusBadRequest, "invalid mod_id")
		return
	}

	switch r.Method {
	case http.MethodPatch:
		endOp, ok := beginHandlerOperation(w, "mod_update", false, "updating mod")
		if !ok {
			return
		}
		defer endOp()
		var req mods.PatchRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeJSONError(w, http.StatusBadRequest, "invalid json")
			return
		}
		resp, err := mods.SetEnabled(serverRoot, modID, req.Enabled)
		if err != nil {
			modsLogf(cfg, "action=set_enabled mod_id=%s enabled=%t status=failed err=%v", modID, req.Enabled, err)
			if errors.Is(err, os.ErrNotExist) {
				writeJSONError(w, http.StatusNotFound, "mod not found")
				return
			}
			writeJSONError(w, http.StatusBadRequest, err.Error())
			return
		}
		modsLogf(cfg, "action=set_enabled mod_id=%s enabled=%t status=ok", modID, req.Enabled)
		writeJSON(w, http.StatusOK, resp)
	case http.MethodDelete:
		endOp, ok := beginHandlerOperation(w, "mod_delete", false, "deleting mod")
		if !ok {
			return
		}
		defer endOp()
		resp, err := mods.DeleteMod(serverRoot, modID)
		if err != nil {
			modsLogf(cfg, "action=delete mod_id=%s status=failed err=%v", modID, err)
			if errors.Is(err, os.ErrNotExist) {
				writeJSONError(w, http.StatusNotFound, "mod not found")
				return
			}
			writeJSONError(w, http.StatusBadRequest, err.Error())
			return
		}
		modsLogf(cfg, "action=delete mod_id=%s status=ok", modID)
		writeJSON(w, http.StatusOK, resp)
	default:
		writeJSONError(w, http.StatusMethodNotAllowed, "PATCH or DELETE only")
	}
}

func requireMinecraftGame(w http.ResponseWriter) (*state.Config, string, bool) {
	cfg, err := state.LoadConfig()
	if err != nil {
		writeJSONError(w, http.StatusBadRequest, "no config loaded")
		return nil, "", false
	}
	if strings.ToLower(cfg.ContainerType) != "game" {
		writeJSONError(w, http.StatusBadRequest, "not a game container")
		return nil, "", false
	}
	if strings.ToLower(cfg.Game) != "minecraft" {
		writeJSONError(w, http.StatusNotImplemented, "unsupported game")
		return nil, "", false
	}
	return cfg, mods.ResolveServerRoot(cfg), true
}

func writeJSON(w http.ResponseWriter, status int, payload any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}

func writeJSONError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, jsonError{Error: msg})
}
