package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strings"
	"sync"

	kvmv1 "github.com/tidbcloud/k8s-version-manager/api/v1"
)

type MockServer struct {
	mu          sync.Mutex
	k8sInfos    map[string]*kvmv1.K8sInformation
	failMap     map[string]bool
	sharedPools []SharedPool
}

func main() {
	server := &MockServer{
		k8sInfos: make(map[string]*kvmv1.K8sInformation),
		failMap:  make(map[string]bool),
	}

	http.HandleFunc("/", server.handleRequest)

	log.Println("Mock server starting on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func (s *MockServer) handleRequest(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s", r.Method, r.URL.Path)

	if strings.HasSuffix(r.URL.Path, ":k8sinfo") && r.Method == http.MethodGet {
		s.getK8sInfo(w, r)
		return
	}
	if strings.HasSuffix(r.URL.Path, ":upgrade") && r.Method == http.MethodPost {
		s.upgrade(w, r)
		return
	}
	if strings.HasSuffix(r.URL.Path, ":deleteNodePool") && r.Method == http.MethodPost {
		s.deleteNodePool(w, r)
		return
	}
	if r.URL.Path == "/mock/set-k8sinfo" && r.Method == http.MethodPost {
		s.setK8sInfo(w, r)
		return
	}
	if r.URL.Path == "/mock/set-fail" && r.Method == http.MethodPost {
		s.setFail(w, r)
		return
	}

	if strings.Contains(r.URL.Path, "api/v1beta/sharedPools") {
		if strings.HasSuffix(r.URL.Path, ":setComponentNodeVersion") && r.Method == http.MethodPost {
			s.setSharedPoolComponentNodeVersion(w, r)
			return
		}
		if r.Method == http.MethodGet {
			s.listSharedPools(w, r)
			return
		}
	}

	if r.URL.Path == "/mock/set-shared-pools" && r.Method == http.MethodPost {
		s.setSharedPools(w, r)
		return
	}

	w.WriteHeader(http.StatusNotFound)
}

func (s *MockServer) getK8sInfo(w http.ResponseWriter, r *http.Request) {
	dpID := s.extractDataPlaneID(r.URL.Path, ":k8sinfo")
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.failMap[dpID] {
		http.Error(w, "Simulated failure", http.StatusInternalServerError)
		return
	}

	info, ok := s.k8sInfos[dpID]
	if !ok {
		// Default info if not set
		info = &kvmv1.K8sInformation{
			TargetVersion: "v1.33",
			ControlPlane: &kvmv1.K8sControlPlane{
				Name:    dpID,
				Version: "v1.33",
			},
			NodePools: []kvmv1.K8sNodePool{
				{
					Kind:    kvmv1.K8sNodePoolKindAdmin,
					Name:    "admin",
					Version: "v1.33",
					Ready:   true,
				},
				{
					Kind:    kvmv1.K8sNodePoolKindWorker,
					Name:    "worker-1",
					Version: "v1.33",
					Ready:   true,
				},
			},
		}
		s.k8sInfos[dpID] = info
	}

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(info); err != nil {
		log.Printf("failed to encode k8s info response for %s: %v", dpID, err)
	}
}

func (s *MockServer) upgrade(w http.ResponseWriter, r *http.Request) {
	dpID := s.extractDataPlaneID(r.URL.Path, ":upgrade")

	s.mu.Lock()
	failed := s.failMap[dpID]
	s.mu.Unlock()

	if failed {
		http.Error(w, "Simulated failure", http.StatusInternalServerError)
		return
	}

	var req struct {
		K8sVersion string `json:"k8s_version"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	log.Printf("Starting upgrade for %s to %s", dpID, req.K8sVersion)

	w.WriteHeader(http.StatusAccepted)
}

func (s *MockServer) deleteNodePool(w http.ResponseWriter, r *http.Request) {
	dpID := s.extractDataPlaneID(r.URL.Path, ":deleteNodePool")

	var req struct {
		// Note: we intentionally reuse the "k8s_version" field from the upgrade API.
		// In this handler it represents the node pool version whose pools should be deleted.
		K8sVersion string `json:"k8s_version"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	if s.failMap[dpID] {
		http.Error(w, "Simulated failure", http.StatusInternalServerError)
		return
	}

	info, ok := s.k8sInfos[dpID]
	if ok {
		var newNodePools []kvmv1.K8sNodePool
		for _, pool := range info.NodePools {
			if pool.Version != req.K8sVersion {
				newNodePools = append(newNodePools, pool)
			}
		}
		info.NodePools = newNodePools
	}

	w.WriteHeader(http.StatusOK)
}

func (s *MockServer) setK8sInfo(w http.ResponseWriter, r *http.Request) {
	var req struct {
		DataPlaneID string               `json:"dataPlaneId"`
		K8sInfo     kvmv1.K8sInformation `json:"k8sInfo"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.k8sInfos[req.DataPlaneID] = &req.K8sInfo

	w.WriteHeader(http.StatusOK)
}

func (s *MockServer) setFail(w http.ResponseWriter, r *http.Request) {
	dataPlaneID := r.URL.Query().Get("dataPlaneId")
	fail := r.URL.Query().Get("fail") == "true"

	s.mu.Lock()
	defer s.mu.Unlock()
	s.failMap[dataPlaneID] = fail

	log.Printf("Setting fail for %s to %v", dataPlaneID, fail)
	w.WriteHeader(http.StatusOK)
}

func (s *MockServer) extractDataPlaneID(path, suffix string) string {
	// Expected format: /api/v1/dataplanes/{id}{suffix}
	parts := strings.Split(path, "/")
	if len(parts) < 5 {
		return ""
	}
	lastPart := parts[4]
	if !strings.HasSuffix(lastPart, suffix) {
		return ""
	}
	return strings.TrimSuffix(lastPart, suffix)
}

// CSNG Mock Definitions

type SharedPool struct {
	ID              string      `json:"id"`
	Version         string      `json:"version"`
	Phase           string      `json:"phase"`
	ComponentGroups []Component `json:"componentGroups"`
}

type Component struct {
	ID            string    `json:"id"`
	ComponentType string    `json:"componentType"`
	Version       string    `json:"version"`
	Phase         string    `json:"phase"`
	NodeQuota     NodeQuota `json:"nodeQuota"`
}

type NodeQuota struct {
	NodeSpec NodeSpec `json:"nodeSpec"`
}

type NodeSpec struct {
	NodeVersion string `json:"nodeVersion"`
}

type ListSharedPoolsResponse struct {
	SharedPools []SharedPool `json:"sharedPools"`
	TotalCount  int32        `json:"totalCount"`
}

func (s *MockServer) listSharedPools(w http.ResponseWriter, _ *http.Request) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// In this simple mock, we just return all pools we have, without filtering.
	// Real implementation filters by dataplane_names.
	// For now let's just return what we have stored via set-shared-pools.
	// We might store them by dataplaneID key if needed, or just a global list for simplicity if tests don't overlap.
	// Let's assume s.sharedPools is []SharedPool

	resp := ListSharedPoolsResponse{
		SharedPools: s.sharedPools,
		TotalCount:  int32(len(s.sharedPools)),
	}

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		log.Printf("failed to encode shared pools response: %v", err)
	}
}

func (s *MockServer) setSharedPoolComponentNodeVersion(w http.ResponseWriter, r *http.Request) {
	// Format: /api/v1beta/sharedPools/{id}:setComponentNodeVersion
	parts := strings.Split(r.URL.Path, "/")
	// parts[0] = ""
	// parts[1] = "api"
	// parts[2] = "v1beta"
	// parts[3] = "sharedPools"
	// parts[4] = "{id}:setComponentNodeVersion"

	if len(parts) < 5 {
		http.Error(w, "invalid path", http.StatusBadRequest)
		return
	}

	poolID := strings.TrimSuffix(parts[4], ":setComponentNodeVersion")
	if poolID == "" {
		http.Error(w, "invalid pool ID", http.StatusBadRequest)
		return
	}

	var req struct {
		ID            string `json:"id"`
		ComponentType string `json:"component_type"`
		NodeVersion   string `json:"node_version"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	// Find the pool and component and update it
	found := false
	for i, sp := range s.sharedPools {
		if sp.ID == poolID {
			for j, comp := range sp.ComponentGroups {
				// Match by Type and ID
				if comp.ComponentType == req.ComponentType && comp.ID == req.ID {
					s.sharedPools[i].ComponentGroups[j].NodeQuota.NodeSpec.NodeVersion = req.NodeVersion
					s.sharedPools[i].ComponentGroups[j].Phase = "MODIFYING"
					found = true
					break
				}
			}
		}
		if found {
			break
		}
	}

	if !found {
		log.Printf("Warning: component %s in pool %s not found for update", req.ID, poolID)
	} else {
		log.Printf("Updated component %s in pool %s to version %s", req.ID, poolID, req.NodeVersion)
	}

	w.WriteHeader(http.StatusOK)
}

func (s *MockServer) setSharedPools(w http.ResponseWriter, r *http.Request) {
	var req struct {
		SharedPools []SharedPool `json:"sharedPools"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.sharedPools = req.SharedPools
	w.WriteHeader(http.StatusOK)
}
