package handlers

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/clustercontext"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/ticdc"
	"github.com/stretchr/testify/require"
)

type ticdcRepositoryStub struct {
	cluster *clustercontext.Cluster
	err     error
}

func (s ticdcRepositoryStub) GetByClusterID(context.Context, string) (*clustercontext.Cluster, error) {
	return s.cluster, s.err
}

func (s ticdcRepositoryStub) GetByOrgAndClusterID(context.Context, uint64, string) (*clustercontext.Cluster, error) {
	return s.cluster, s.err
}

func (s ticdcRepositoryStub) ListByOrg(context.Context, uint64, clustercontext.ListOptions) (*clustercontext.Page, error) {
	return nil, s.err
}

type ticdcServiceStub struct {
	listCalls        int
	lastArch         ticdc.Architecture
	lastChangefeedID string
}

func (s *ticdcServiceStub) List(_ context.Context, _, _, _ uint64, arch ticdc.Architecture) (*ticdc.ListResponse, error) {
	s.listCalls++
	s.lastArch = arch
	return &ticdc.ListResponse{Changefeeds: []*ticdc.Changefeed{}}, nil
}

func (s *ticdcServiceStub) Detail(_ context.Context, _ uint64, _ uint64, _ uint64, changefeedID string, architecture ticdc.Architecture) (*ticdc.DetailResponse, error) {
	s.lastChangefeedID = changefeedID
	s.lastArch = architecture
	return &ticdc.DetailResponse{Changefeed: &ticdc.Changefeed{}}, nil
}

func TestTiCDCHandlerInternalEssentialUsesServerlessArchitecture(t *testing.T) {
	gin.SetMode(gin.TestMode)
	service := &ticdcServiceStub{}
	repository := ticdcRepositoryStub{cluster: &clustercontext.Cluster{
		ClusterID: "456", OrgID: "123", DeployType: "shared", DeployTypeV2: "essential",
		DiagnosticContext: clustercontext.DiagnosticContext{ProjectID: "789"},
	}}
	router := gin.New()
	NewTiCDCHandler(repository, service).RegisterRoutes(router)

	internal := httptest.NewRecorder()
	router.ServeHTTP(internal, httptest.NewRequest(http.MethodGet,
		"/data-proxy/api/v1/orgs/123/clusters/456/ticdc/changefeeds", nil))
	require.Equal(t, http.StatusOK, internal.Code, internal.Body.String())
	require.Equal(t, 1, service.listCalls)
	require.Equal(t, ticdc.ArchitectureEssential, service.lastArch)

	public := httptest.NewRecorder()
	router.ServeHTTP(public, httptest.NewRequest(http.MethodGet,
		"/api/v1/orgs/123/data-proxy/clusters/456/ticdc/changefeeds", nil))
	require.Equal(t, http.StatusForbidden, public.Code)
}

func TestTiCDCHandlerInternalEssentialDetailAcceptsOpaqueID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	service := &ticdcServiceStub{}
	repository := ticdcRepositoryStub{cluster: &clustercontext.Cluster{
		ClusterID: "456", OrgID: "123", DeployType: "shared", DeployTypeV2: "essential",
		DiagnosticContext: clustercontext.DiagnosticContext{ProjectID: "789"},
	}}
	router := gin.New()
	NewTiCDCHandler(repository, service).RegisterRoutes(router)

	response := httptest.NewRecorder()
	router.ServeHTTP(response, httptest.NewRequest(http.MethodGet,
		"/data-proxy/api/v1/orgs/123/clusters/456/ticdc/changefeeds/cf-opaque-id", nil))
	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.Equal(t, "cf-opaque-id", service.lastChangefeedID)
	require.Equal(t, ticdc.ArchitectureEssential, service.lastArch)
}

func TestTiCDCHandlerRegistersInternalAndPublicRoutes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	NewTiCDCHandler(ticdcRepositoryStub{}, &ticdcServiceStub{}).RegisterRoutes(router)

	want := map[string]struct{}{
		http.MethodGet + " /data-proxy/api/v1/orgs/:orgId/clusters/:clusterId/ticdc/changefeeds":               {},
		http.MethodGet + " /data-proxy/api/v1/orgs/:orgId/clusters/:clusterId/ticdc/changefeeds/:changefeedId": {},
		http.MethodGet + " /api/v1/orgs/:orgId/data-proxy/clusters/:clusterId/ticdc/changefeeds":               {},
		http.MethodGet + " /api/v1/orgs/:orgId/data-proxy/clusters/:clusterId/ticdc/changefeeds/:changefeedId": {},
	}
	for _, route := range router.Routes() {
		delete(want, route.Method+" "+route.Path)
	}
	require.Empty(t, want)
}

func TestTiCDCHandlerPublicRejectsNextGenButInternalUsesIt(t *testing.T) {
	gin.SetMode(gin.TestMode)
	for _, test := range []struct {
		name    string
		cluster *clustercontext.Cluster
		want    ticdc.Architecture
	}{
		{name: "premium", cluster: &clustercontext.Cluster{DeployType: "premium"}, want: ticdc.ArchitectureNextGen},
		{name: "starter", cluster: &clustercontext.Cluster{DeployType: "starter"}, want: ticdc.ArchitectureNextGen},
		{name: "essential", cluster: &clustercontext.Cluster{DeployType: "shared", DeployTypeV2: "essential"}, want: ticdc.ArchitectureEssential},
	} {
		t.Run(test.name, func(t *testing.T) {
			service := &ticdcServiceStub{}
			test.cluster.ClusterID = "456"
			test.cluster.OrgID = "123"
			test.cluster.DiagnosticContext = clustercontext.DiagnosticContext{ProjectID: "789"}
			repository := ticdcRepositoryStub{cluster: test.cluster}
			router := gin.New()
			NewTiCDCHandler(repository, service).RegisterRoutes(router)

			public := httptest.NewRecorder()
			router.ServeHTTP(public, httptest.NewRequest(http.MethodGet,
				"/api/v1/orgs/123/data-proxy/clusters/456/ticdc/changefeeds", nil))
			require.Equal(t, http.StatusForbidden, public.Code)
			require.Zero(t, service.listCalls)

			internal := httptest.NewRecorder()
			router.ServeHTTP(internal, httptest.NewRequest(http.MethodGet,
				"/data-proxy/api/v1/orgs/123/clusters/456/ticdc/changefeeds", nil))
			require.Equal(t, http.StatusOK, internal.Code, internal.Body.String())
			require.Equal(t, 1, service.listCalls)
			require.Equal(t, test.want, service.lastArch)
		})
	}
}

func TestTiCDCHandlerRejectsInvalidCanonicalProjectIDBeforeUpstream(t *testing.T) {
	gin.SetMode(gin.TestMode)
	service := &ticdcServiceStub{}
	repository := ticdcRepositoryStub{cluster: &clustercontext.Cluster{
		ClusterID: "456", OrgID: "123", DeployType: "dedicated",
		DiagnosticContext: clustercontext.DiagnosticContext{ProjectID: "invalid"},
	}}
	router := gin.New()
	NewTiCDCHandler(repository, service).RegisterRoutes(router)

	response := httptest.NewRecorder()
	router.ServeHTTP(response, httptest.NewRequest(http.MethodGet,
		"/api/v1/orgs/123/data-proxy/clusters/456/ticdc/changefeeds", nil))
	require.Equal(t, http.StatusBadGateway, response.Code)
	require.Zero(t, service.listCalls)
}
