package integration

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	storagev1 "k8s.io/api/storage/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	"github.com/tidbcloud/eks-provider/controllers/utils/configmanager"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/config"
)

// noopProxyPDAuth is passed to workflow.WithPDAuth in integration tests; the fake dataplane endpoint does not exercise real proxy auth.
var noopProxyPDAuth configmanager.PDAuth = func(*http.Request) error { return nil }

func fakeClient(scheme *runtime.Scheme, current ...client.Object) client.Client {
	return fake.NewClientBuilder().WithScheme(scheme).WithObjects(current...).Build()
}

type fakeSQLExecutor struct {
	sqlSet         map[string]bool
	sqlQueryResult map[string]string
}

func newFakeSQLExecutor() *fakeSQLExecutor {
	return &fakeSQLExecutor{
		sqlSet:         make(map[string]bool),
		sqlQueryResult: make(map[string]string),
	}
}

func (f *fakeSQLExecutor) Execute(ctx context.Context, sql string) error {
	f.sqlSet[sql] = true
	return nil
}

func (f *fakeSQLExecutor) Query(ctx context.Context, sql string) ([]byte, error) {
	return []byte(f.sqlQueryResult[sql]), nil
}

type fakeClusterMutator struct {
	decoder admission.Decoder
}

func newFakeClusterMutator(mgr ctrl.Manager) *fakeClusterMutator {
	return &fakeClusterMutator{
		decoder: admission.NewDecoder(mgr.GetScheme()),
	}
}

func (f *fakeClusterMutator) Handle(ctx context.Context, req admission.Request) admission.Response {
	cluster := &cloudv1.Cluster{}
	err := f.decoder.Decode(req, cluster)
	if err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}

	origin, err := json.Marshal(cluster)
	if err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}
	return admission.PatchResponseFromRaw(origin, origin)
}

type fakeAzureCloudConfig struct {
	instanceTypes  map[string]cloudv1.InstanceType
	zones          []string
	storageClasses []storagev1.StorageClass
}

func newFakeAzureCloudConfig() config.CloudConfigProvider {
	return &fakeAzureCloudConfig{
		instanceTypes: map[string]cloudv1.InstanceType{
			instanceType: {
				CPU: resource.MustParse("8"),
			},
			instanceType2: {
				CPU: resource.MustParse("16"),
			},
		},
		zones: []string{"moon-1", "moon-2", "moon-3"},
	}
}

func (c *fakeAzureCloudConfig) GetProviderType() string {
	return "aks"
}

func (c *fakeAzureCloudConfig) GetInstanceTypes() ([]cloudv1.InstanceType, error) {
	var its []cloudv1.InstanceType
	for _, it := range c.instanceTypes {
		its = append(its, it)
	}
	return its, nil
}

func (c *fakeAzureCloudConfig) GetStorageClasses() ([]*storagev1.StorageClass, error) {
	var sc []*storagev1.StorageClass
	for idx := range c.storageClasses {
		sc = append(sc, &c.storageClasses[idx])
	}
	return sc, nil
}

func (c *fakeAzureCloudConfig) FindInstanceType(name string) (cloudv1.InstanceType, error) {
	if it, ok := c.instanceTypes[name]; ok {
		return it, nil
	}
	return cloudv1.InstanceType{}, fmt.Errorf("instance type %s not found", name)
}

func (c *fakeAzureCloudConfig) FindRegionZones(region string) ([]string, error) {
	return c.zones, nil
}

type fakePDClient struct{}

func (c *fakePDClient) GetConfig() (map[string]interface{}, error) {
	return nil, nil
}

func (c *fakePDClient) PutConfig(conf map[string]interface{}) error {
	return nil
}

func (c *fakePDClient) HTTPGet(path string) ([]byte, error) {
	return nil, nil
}

func (c *fakePDClient) HTTPPost(path string, body []byte) error {
	return nil
}

type fakeDataplaneHTTPEndpoint struct{}

func (e *fakeDataplaneHTTPEndpoint) GetProxyURL(ctx context.Context, clusterName string) (string, error) {
	return "", nil
}

func (e *fakeDataplaneHTTPEndpoint) GetPDClient(ctx context.Context, httpCli *http.Client, pdAuthFunc configmanager.PDAuth, clusterName string, dryRun bool) (configmanager.DynamicPDClient, error) {
	return nil, nil
}
