package cluster

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gorilla/mux"
	. "github.com/onsi/gomega"
	"github.com/prometheus/client_golang/prometheus/testutil"
	"github.com/tidbcloud/aws-shared-provider/pkg/metrics"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/query"
	infra "github.com/tidbcloud/tidb-management-service/api/serverless_infra"
	"github.com/tidbcloud/tidb-management-service/api/serverless_infra/meta"
)

// newFakeHTTPWithRuVersionStatus creates a fake HTTP server where the
// ru-version endpoint returns the given status code.
func newFakeHTTPWithRuVersionStatus(ruVersionStatus int) *httptest.Server {
	router := mux.NewRouter()
	router.HandleFunc("/pd/api/v2/keyspaces/{keyspace}", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}).Methods("GET")
	router.HandleFunc("/pd/api/v2/keyspaces/{keyspace}/config", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}).Methods("PATCH")
	router.HandleFunc("/pd/api/v2/keyspaces/{keyspace}/state", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}).Methods("PUT")
	router.HandleFunc("/resource-manager/api/v1/config/group", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}).Methods("POST", "PUT")
	router.HandleFunc("/resource-manager/api/v1/config/group/{group}", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}).Methods("DELETE")
	router.HandleFunc("/resource-manager/api/v1/config/controller/ru-version/{keyspace}", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(ruVersionStatus)
	}).Methods("POST")
	router.HandleFunc("/api/v1/query", func(w http.ResponseWriter, r *http.Request) {
		var response query.PromResponse
		_ = json.NewEncoder(w).Encode(response)
		w.WriteHeader(http.StatusOK)
	}).Methods("GET")

	return httptest.NewServer(router)
}

func TestEnsureRuVersion_404_RuVersion2_EmitsMetric(t *testing.T) {
	g := NewGomegaWithT(t)

	httpServer := newFakeHTTPWithRuVersionStatus(http.StatusNotFound)
	defer httpServer.Close()

	cluster := &infra.Cluster{
		ObjectMeta: meta.ObjectMeta{
			Name:        "test-cluster-ruv2-404",
			Namespace:   "test-ns",
			Annotations: map[string]string{},
		},
		Spec: infra.ClusterSpec{
			RuVersion: 2,
		},
		Status: infra.ClusterStatus{
			Phase:         infra.ClusterPhaseRunning,
			TidbPool:      defaultTidbPool,
			KeyspaceToken: "ks-token",
		},
	}

	r := newFakeReconcileContext(cluster, nil, httpServer)

	metrics.RuVersionUnsupportedTotal.Reset()

	err := r.ensureRuVersion()
	g.Expect(err).To(Succeed())

	// Metric should be incremented for ruVersion=2 + 404
	metricValue := testutil.ToFloat64(metrics.RuVersionUnsupportedTotal.WithLabelValues("test-cluster-ruv2-404", defaultTidbPool))
	g.Expect(metricValue).To(Equal(float64(1)))

	// Annotation should still be written (graceful skip)
	g.Expect(cluster.Annotations[types.AnnoKeyPrevRuVersion]).To(Equal("2"))
}

func TestEnsureRuVersion_404_RuVersion3_NoMetric(t *testing.T) {
	g := NewGomegaWithT(t)

	httpServer := newFakeHTTPWithRuVersionStatus(http.StatusNotFound)
	defer httpServer.Close()

	cluster := &infra.Cluster{
		ObjectMeta: meta.ObjectMeta{
			Name:        "test-cluster-ruv3-404",
			Namespace:   "test-ns",
			Annotations: map[string]string{},
		},
		Spec: infra.ClusterSpec{
			RuVersion: 3,
		},
		Status: infra.ClusterStatus{
			Phase:         infra.ClusterPhaseRunning,
			TidbPool:      defaultTidbPool,
			KeyspaceToken: "ks-token",
		},
	}

	r := newFakeReconcileContext(cluster, nil, httpServer)

	metrics.RuVersionUnsupportedTotal.Reset()

	err := r.ensureRuVersion()
	g.Expect(err).To(Succeed())

	// Metric should NOT be incremented for ruVersion=3
	metricValue := testutil.ToFloat64(metrics.RuVersionUnsupportedTotal.WithLabelValues("test-cluster-ruv3-404", defaultTidbPool))
	g.Expect(metricValue).To(Equal(float64(0)))
}

func TestEnsureRuVersion_Success_NoMetric(t *testing.T) {
	g := NewGomegaWithT(t)

	httpServer := newFakeHTTPWithRuVersionStatus(http.StatusOK)
	defer httpServer.Close()

	cluster := &infra.Cluster{
		ObjectMeta: meta.ObjectMeta{
			Name:        "test-cluster-ruv2-ok",
			Namespace:   "test-ns",
			Annotations: map[string]string{},
		},
		Spec: infra.ClusterSpec{
			RuVersion: 2,
		},
		Status: infra.ClusterStatus{
			Phase:         infra.ClusterPhaseRunning,
			TidbPool:      defaultTidbPool,
			KeyspaceToken: "ks-token",
		},
	}

	r := newFakeReconcileContext(cluster, nil, httpServer)

	metrics.RuVersionUnsupportedTotal.Reset()

	err := r.ensureRuVersion()
	g.Expect(err).To(Succeed())

	// Metric should NOT be incremented on success
	metricValue := testutil.ToFloat64(metrics.RuVersionUnsupportedTotal.WithLabelValues("test-cluster-ruv2-ok", defaultTidbPool))
	g.Expect(metricValue).To(Equal(float64(0)))
}

func TestEnsureRuVersion_SkipsWhenAnnotationAlreadySet(t *testing.T) {
	g := NewGomegaWithT(t)

	httpServer := newFakeHTTPWithRuVersionStatus(http.StatusNotFound)
	defer httpServer.Close()

	cluster := &infra.Cluster{
		ObjectMeta: meta.ObjectMeta{
			Name:      "test-cluster-skip",
			Namespace: "test-ns",
			Annotations: map[string]string{
				types.AnnoKeyPrevRuVersion: "2",
			},
		},
		Spec: infra.ClusterSpec{
			RuVersion: 2,
		},
		Status: infra.ClusterStatus{
			Phase:         infra.ClusterPhaseRunning,
			TidbPool:      defaultTidbPool,
			KeyspaceToken: "ks-token",
		},
	}

	r := newFakeReconcileContext(cluster, nil, httpServer)

	metrics.RuVersionUnsupportedTotal.Reset()

	err := r.ensureRuVersion()
	g.Expect(err).To(Succeed())

	// Metric should NOT be incremented when annotation is already set (early return)
	metricValue := testutil.ToFloat64(metrics.RuVersionUnsupportedTotal.WithLabelValues("test-cluster-skip", defaultTidbPool))
	g.Expect(metricValue).To(Equal(float64(0)))
}
