package clustercontext

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

	"github.com/stretchr/testify/require"

	clinic_internal_api "github.com/pingcap-inc/clinic/clinic_data_service/pkg/clinic-internal-api"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/tidbmgmthttp"
)

type fakeClinicMetadataClient struct {
	cluster *clinic_internal_api.ClusterMetadata
	page    *clinic_internal_api.ClusterMetadataPage
	err     error
	query   url.Values
}

type fakeUserVisibilityClient struct {
	request  tidbmgmthttp.ListRequest
	response *tidbmgmthttp.ListResponse
	err      error
}

func (f *fakeUserVisibilityClient) ListTidbs(_ context.Context, request tidbmgmthttp.ListRequest) (*tidbmgmthttp.ListResponse, error) {
	f.request = request
	return f.response, f.err
}

func (f *fakeClinicMetadataClient) GetClusterMetadata(context.Context, string) (*clinic_internal_api.ClusterMetadata, error) {
	return f.cluster, f.err
}

func (f *fakeClinicMetadataClient) GetOrgClusterMetadata(context.Context, uint64, string) (*clinic_internal_api.ClusterMetadata, error) {
	return f.cluster, f.err
}

func (f *fakeClinicMetadataClient) ListOrgClusterMetadata(_ context.Context, _ uint64, query url.Values) (*clinic_internal_api.ClusterMetadataPage, error) {
	f.query = query
	return f.page, f.err
}

func TestClinicRepositoryMapsCanonicalMetadataAndListQuery(t *testing.T) {
	client := &fakeClinicMetadataClient{
		cluster: &clinic_internal_api.ClusterMetadata{
			ClusterID: "cluster-a", OrgID: "123", Provider: "aws",
			DiagnosticContext: clinic_internal_api.DiagnosticContext{ProjectID: "project-a"},
		},
		page: &clinic_internal_api.ClusterMetadataPage{
			Items: []clinic_internal_api.ClusterMetadata{{ClusterID: "cluster-a", OrgID: "123"}},
			Page:  2, PageSize: 25, Total: 26,
		},
	}
	repository := NewClinicRepository(client)

	cluster, err := repository.GetByOrgAndClusterID(context.Background(), 123, "cluster-a")
	require.NoError(t, err)
	require.Equal(t, "aws", cluster.Provider)
	require.Equal(t, "project-a", cluster.DiagnosticContext.ProjectID)

	page, err := repository.ListByOrg(context.Background(), 123, ListOptions{Page: 2, PageSize: 25, Query: "cluster", ShowDeleted: true})
	require.NoError(t, err)
	require.Equal(t, int64(26), page.Total)
	require.Equal(t, "2", client.query.Get("page"))
	require.Equal(t, "25", client.query.Get("pageSize"))
	require.Equal(t, "cluster", client.query.Get("query"))
	require.Equal(t, "true", client.query.Get("showDeleted"))
}

func TestClinicRepositoryClassifiesInternalStatus(t *testing.T) {
	for status, want := range map[int]error{
		http.StatusNotFound:           ErrNotFound,
		http.StatusServiceUnavailable: ErrUnavailable,
		http.StatusGatewayTimeout:     ErrTimeout,
	} {
		client := &fakeClinicMetadataClient{err: &clinic_internal_api.UpstreamError{StatusCode: status}}
		_, err := NewClinicRepository(client).GetByClusterID(context.Background(), "cluster-a")
		require.ErrorIs(t, err, want, "Clinic status %d", status)
	}
}

func TestUserScopedRepositoryUsesHTTPVisibilityAndCanonicalMetadata(t *testing.T) {
	metadataClient := &fakeClinicMetadataClient{cluster: &clinic_internal_api.ClusterMetadata{ClusterID: "cluster-a", OrgID: "123", Name: "visible"}}
	visibilityClient := &fakeUserVisibilityClient{response: &tidbmgmthttp.ListResponse{Tidbs: []tidbmgmthttp.Tidb{{TidbID: "cluster-a"}}, TotalSize: 31}}
	repository := NewUserScopedRepository(metadataClient, visibilityClient)

	page, err := repository.ListByOrgForUser(context.Background(), 123, ListOptions{Page: 2, PageSize: 25}, "Bearer token", map[string][]string{"Traceparent": {"00-abc-def-01"}})
	require.NoError(t, err)
	require.Equal(t, 25, visibilityClient.request.Skip)
	require.Equal(t, 25, visibilityClient.request.PageSize)
	require.Equal(t, "Bearer token", visibilityClient.request.Authorization)
	require.Equal(t, int64(31), page.Total)
	require.Equal(t, []Cluster{{ClusterID: "cluster-a", OrgID: "123", Name: "visible", DiagnosticContext: DiagnosticContext{}}}, page.Items)
}

func TestUserScopedRepositoryFailsClosedOnMismatchedMetadata(t *testing.T) {
	metadataClient := &fakeClinicMetadataClient{cluster: &clinic_internal_api.ClusterMetadata{ClusterID: "other", OrgID: "123"}}
	visibilityClient := &fakeUserVisibilityClient{response: &tidbmgmthttp.ListResponse{Tidbs: []tidbmgmthttp.Tidb{{TidbID: "cluster-a"}}}}
	_, err := NewUserScopedRepository(metadataClient, visibilityClient).ListByOrgForUser(context.Background(), 123, ListOptions{Page: 1, PageSize: 1}, "Bearer token", nil)
	require.ErrorIs(t, err, ErrBadGateway)
}

func TestUserScopedRepositoryClassifiesHTTPAuthorizationErrors(t *testing.T) {
	visibilityClient := &fakeUserVisibilityClient{err: &tidbmgmthttp.UpstreamError{StatusCode: http.StatusForbidden}}
	_, err := NewUserScopedRepository(&fakeClinicMetadataClient{}, visibilityClient).ListByOrgForUser(context.Background(), 123, ListOptions{Page: 1, PageSize: 1}, "Bearer token", nil)
	require.ErrorIs(t, err, ErrPermissionDenied)
}

func TestUserScopedRepositoryRejectsInvalidHTTPPage(t *testing.T) {
	visibilityClient := &fakeUserVisibilityClient{response: &tidbmgmthttp.ListResponse{TotalSize: -1}}
	_, err := NewUserScopedRepository(&fakeClinicMetadataClient{}, visibilityClient).ListByOrgForUser(context.Background(), 123, ListOptions{Page: 1, PageSize: 1}, "Bearer token", nil)
	require.ErrorIs(t, err, ErrBadGateway)
}
