package k8s

import (
	"context"
	"errors"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	compatcst "github.com/tidbcloud/infra-provider/pkg/compat/consts"
	"github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/cluster"
	"github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/network"
	"github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/o11ycluster"
	"github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/provider"
	models "github.com/tidbcloud/infra-provider/pkg/compat/models"
	"github.com/tidbcloud/infra-provider/pkg/config"
	agentv1 "github.com/tidbcloud/observability/agent-control-server/api/v1"
	"github.com/tidbcloud/tiinf/transport/crossregion"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ client.Client = &CompatK8sClient{}

// CompatK8sClient is a wrapper for controller runtime k8s client
type CompatK8sClient struct {
	*CompatK8sReader
	cli           client.Client
	networkcli    network.CompatNetworkClienter
	clustercli    cluster.CompatClusterClienter
	providercli   provider.CompatProviderClienter
	o11yclusercli o11ycluster.CompatO11yClusterClienter
}

// NewCompatK8sClient returns a new CompatK8sClient
func NewCompatK8sClient(cli client.Client, env crossregion.EnvType, roleName string, dataPlaneRoleArn string, assumeExternalId string, override config.OverrideConfig, clusterEndpoint string) *CompatK8sClient {
	return &CompatK8sClient{
		CompatK8sReader: NewCompatK8sReader(cli, env, roleName, dataPlaneRoleArn, assumeExternalId, override, clusterEndpoint),
		cli:             cli,
		networkcli:      network.NewCompatNetworkClient(env, roleName, dataPlaneRoleArn, assumeExternalId),
		clustercli:      cluster.NewCompatClusterClient(env, override, clusterEndpoint),
		providercli:     provider.NewCompatProviderClient(env),
	}
}

// NewCompatK8sClient returns a new CompatK8sClient with o11y client
func NewCompatK8sClientWithO11y(cli client.Client, env crossregion.EnvType, roleName string, dataPlaneRoleArn string, assumeExternalId string, override config.OverrideConfig, providername, region string, o11ycli o11ycluster.O11yInfraAPI, clusterEndpoint string) *CompatK8sClient {
	return &CompatK8sClient{
		CompatK8sReader: NewCompatK8sReaderWithO11y(cli, env, roleName, dataPlaneRoleArn, assumeExternalId, override, providername, region, o11ycli, clusterEndpoint),
		cli:             cli,
		networkcli:      network.NewCompatNetworkClient(env, roleName, dataPlaneRoleArn, assumeExternalId),
		clustercli:      cluster.NewCompatClusterClient(env, override, clusterEndpoint),
		providercli:     provider.NewCompatProviderClient(env),
		o11yclusercli:   o11ycluster.NewCompatO11yClusterClient(providername, region, env, o11ycli),
	}
}

func (ckc CompatK8sClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
	if o11ycluster, ok := obj.(*agentv1.O11yCluster); ok {
		if _, ok := o11ycluster.Annotations[compatcst.CompatFlagAnnotation]; !ok {
			return ckc.cli.Create(ctx, obj, opts...)
		}
		return ckc.o11yclusercli.CreateO11yCluster(ctx, obj, opts...)
	}
	return ckc.cli.Create(ctx, obj, opts...)
}

func (ckc CompatK8sClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
	if o11ycluster, ok := obj.(*agentv1.O11yCluster); ok {
		if _, ok := o11ycluster.Annotations[compatcst.CompatFlagAnnotation]; !ok {
			return ckc.cli.Delete(ctx, obj, opts...)
		}
		return ckc.o11yclusercli.DeleteO11yCluster(ctx, obj, opts...)
	}
	return ckc.cli.Delete(ctx, obj, opts...)
}

func (ckc CompatK8sClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
	if o11ycluster, ok := obj.(*agentv1.O11yCluster); ok {
		if _, ok := o11ycluster.Annotations[compatcst.CompatFlagAnnotation]; !ok {
			return ckc.cli.Update(ctx, obj, opts...)
		}
		// only pause and resume will call update
		if o11ycluster.Spec.Pause {
			return ckc.o11yclusercli.PauseO11yCluster(ctx, obj, opts...)
		} else {
			return ckc.o11yclusercli.ResumeO11yCluster(ctx, obj, opts...)
		}
	}
	return ckc.cli.Update(ctx, obj, opts...)
}

func (ckc CompatK8sClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
	return ckc.cli.Patch(ctx, obj, patch, opts...)
}

func (ckc CompatK8sClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
	return ckc.cli.DeleteAllOf(ctx, obj, opts...)
}

func (ckc CompatK8sClient) Status() client.StatusWriter {
	return ckc.cli.Status()
}

func (ckc CompatK8sClient) SubResource(subResource string) client.SubResourceClient {
	return ckc.cli.SubResource(subResource)
}

func (ckc CompatK8sClient) RESTMapper() meta.RESTMapper {
	return ckc.cli.RESTMapper()
}

func (ckc CompatK8sClient) Scheme() *runtime.Scheme {
	return ckc.cli.Scheme()
}

func (ckc CompatK8sClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
	return ckc.cli.GroupVersionKindFor(obj)
}

func (ckc CompatK8sClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
	return ckc.cli.IsObjectNamespaced(obj)
}

// CompatK8sReadert is a wrapper for controller runtime k8s client reader
type CompatK8sReader struct {
	cli           client.Reader
	networkcli    network.CompatNetworkClienter
	clustercli    cluster.CompatClusterClienter
	providercli   provider.CompatProviderClienter
	o11yclusercli o11ycluster.CompatO11yClusterClienter
}

// NewCompatK8sClient returns a new CompatK8sClient
func NewCompatK8sReader(cli client.Reader, env crossregion.EnvType, roleName string, dataPlaneRoleArn string, assumeExternalId string, override config.OverrideConfig, clusterEndpoint string) *CompatK8sReader {
	return &CompatK8sReader{
		cli:         cli,
		networkcli:  network.NewCompatNetworkClient(env, roleName, dataPlaneRoleArn, assumeExternalId),
		clustercli:  cluster.NewCompatClusterClient(env, override, clusterEndpoint),
		providercli: provider.NewCompatProviderClient(env),
	}
}

// NewCompatK8sReaderWithO11y returns a new CompatK8sClient with o11y client
func NewCompatK8sReaderWithO11y(cli client.Reader, env crossregion.EnvType, roleName string, dataPlaneRoleArn string, assumeExternalId string, override config.OverrideConfig, providername, region string, o11ycli o11ycluster.O11yInfraAPI, clusterEndpoint string) *CompatK8sReader {
	return &CompatK8sReader{
		cli:           cli,
		networkcli:    network.NewCompatNetworkClient(env, roleName, dataPlaneRoleArn, assumeExternalId),
		clustercli:    cluster.NewCompatClusterClient(env, override, clusterEndpoint),
		providercli:   provider.NewCompatProviderClient(env),
		o11yclusercli: o11ycluster.NewCompatO11yClusterClient(providername, region, env, o11ycli),
	}
}

func (ckc CompatK8sReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
	err := ckc.cli.Get(ctx, key, obj, opts...)
	if err == nil && !isMigratedCR(obj) {
		// for provider we can't get all from new path
		// so we check label and merge from new path
		if provider, ok := obj.(*cloudv1.Provider); ok {
			if _, ok := provider.Annotations[compatcst.CompatFlagAnnotation]; !ok {
				return nil
			}
			return ckc.providercli.GetProvider(ctx, key, obj, opts...)
		}
		if cluster, ok := obj.(*cloudv1.Cluster); ok {
			// new path cluster CRD don't have networkName
			// this cluster CRD will move to regional api server in future, we add this logic to compatible for dev
			providerType := cluster.GetLabels()["cloud.pingcap.com/provider-type"]
			// serverless need to skip this logic
			if cluster.Spec.NetworkName == "" && providerType != "aws-free-tier" {
				*cluster = cloudv1.Cluster{}
				return ckc.clustercli.GetCluster(ctx, key, obj, opts...)
			}
		}
		return nil
	}
	// other error types should be returned directly
	if client.IgnoreNotFound(err) != nil {
		return err
	}
	// if the object is not found, try to get it from new path
	switch obj.(type) {
	case *cloudv1.Network:
		{
			return ckc.networkcli.GetNetwork(ctx, key, obj, opts...)
		}
	case *cloudv1.Cluster:
		{
			return ckc.clustercli.GetCluster(ctx, key, obj, opts...)
		}
	case *agentv1.O11yCluster:
		{
			return ckc.o11yclusercli.GetO11yCluster(ctx, key, obj, opts...)
		}
	}
	return err
}

func (ckc CompatK8sReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
	return ckc.cli.List(ctx, list, opts...)
}

func GetPrivateSubnetCompat(ctx context.Context, reader client.Reader, network *cloudv1.Network, zones []string) ([]models.Globaldedicatedv1CompatNetworkAwsNodeSubnet, error) {
	compatreader, ok := reader.(*CompatK8sReader)
	if !ok {
		return nil, errors.New("reader is not CompatK8sReader")
	}
	subnets, err := compatreader.networkcli.GetPrivateSubnet(ctx, network)
	if err != nil {
		return nil, err
	}
	var result []models.Globaldedicatedv1CompatNetworkAwsNodeSubnet
	for i, subnet := range subnets {
		for _, zone := range zones {
			if subnet.AvailabilityZone == zone {
				result = append(result, subnets[i])
				break
			}
		}
	}
	return result, nil
}

func isMigratedCR(obj client.Object) bool {
	labels := obj.GetLabels()
	if len(labels) == 0 {
		return false
	}

	if val, ok := labels["dedicated.regionalserver.tidbcloud.com/migrated"]; ok && val == "true" {
		return true
	}
	return false
}
