package env

import (
	"context"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
	"github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/tests/e2e/dag"
	"github.com/tidbcloud/eks-provider/tests/e2e/data"
	"github.com/tidbcloud/eks-provider/tests/e2e/label"
	"github.com/tidbcloud/eks-provider/tests/e2e/scheme"
	"github.com/tidbcloud/eks-provider/tests/e2e/suite"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

func init() {
	suite.AddEnv(
		label.New(NewConfigEnv(suite.GetOptions(), CloudAWS), "setup config for AWS", label.CloudAWS),
		label.New(NewConfigEnv(suite.GetOptions(), CloudGCP), "setup config for GCP", label.CloudGCP),
	)
}

type Config struct {
	InfraAPIClient client.Client
	BaseClient     client.Client

	Region string

	ReuseNetwork string
	ReuseProject string

	AWSProfile string
	AWSConfig  aws.Config
}

type ConfigEnv struct {
	Config *Config

	opt   *suite.Options
	cloud string

	skipCleanup bool
}

func NewConfigEnv(opt *suite.Options, cloud string) dag.Env {
	return &ConfigEnv{
		opt:         opt,
		cloud:       cloud,
		skipCleanup: true,
	}
}

func (e *ConfigEnv) Name() string {
	return WithCloud("Config", e.cloud)
}

func (e *ConfigEnv) Setup(ctx context.Context) error {
	e.Config = &Config{}
	infraAPIClient, err := newClient(e.opt.InfraAPIConfig, e.opt.InfraAPIContext)
	if err != nil {
		return fmt.Errorf("can't new infra api client: %w", err)
	}
	e.Config.InfraAPIClient = infraAPIClient

	baseClient, err := newClient(e.opt.BaseConfig, e.opt.BaseContext)
	if err != nil {
		return fmt.Errorf("can't new base client: %w", err)
	}
	e.Config.BaseClient = baseClient
	e.skipCleanup = false

	switch e.cloud {
	case CloudAWS:
		e.Config.Region = e.opt.AWS.Region
		e.Config.ReuseNetwork = e.opt.AWS.ReuseNetwork
		e.Config.AWSProfile = e.opt.AWS.Profile

		acct := cloudv1.Account{
			ObjectMeta: metav1.ObjectMeta{
				Name: e.opt.AWS.Account,
			},
		}
		if err := infraAPIClient.Get(ctx, client.ObjectKeyFromObject(&acct), &acct); err != nil {
			return err
		}
		// TODO: randomize ident
		//nolint: goconst
		ident := "test"
		pc := data.NewProviderConfig(ident, &acct)
		if err := infraAPIClient.Create(ctx, pc); err != nil {
			return err
		}

		acfg, err := awsconfig.LoadDefaultConfig(ctx,
			awsconfig.WithRegion(e.Config.Region),
			awsconfig.WithSharedConfigProfile(e.Config.AWSProfile),
			awsconfig.WithAssumeRoleCredentialOptions(func(opt *stscreds.AssumeRoleOptions) {
				opt.RoleARN = acct.Spec.AWS.AssumeRoleARN
				if acct.Spec.AWS.ExternalID != nil {
					opt.ExternalID = &acct.Spec.AWS.AccountID
				}
			}))
		if err != nil {
			return fmt.Errorf("cannot load aws config: %w", err)
		}
		e.Config.AWSConfig = acfg

	case CloudGCP:
		e.Config.Region = e.opt.GCP.Region
		e.Config.ReuseNetwork = e.opt.GCP.ReuseNetwork
	default:
		return fmt.Errorf("invalid cloud %s", e.cloud)
	}

	return nil
}

func (e *ConfigEnv) Envs() []string {
	return nil
}

func (e *ConfigEnv) Cleanup(ctx context.Context) error {
	if e.skipCleanup {
		return nil
	}
	errList := []error{}
	if e.cloud == CloudAWS {
		// TODO: randomize ident
		//nolint: goconst
		ident := "test"
		usl := resource.MustCreateObject(v1beta1.ProviderConfigUsageListGroupVersionKind, scheme.Scheme).(resource.ProviderConfigUsageList)
		if err := e.Config.InfraAPIClient.List(ctx, usl, client.MatchingLabels{
			xpv1.LabelKeyProviderName: data.ProviderConfigName(ident),
		}); err != nil {
			errList = append(errList, err)
		}

		for _, item := range usl.GetItems() {
			ref := item.GetResourceReference()
			obj := resource.MustCreateObject(ref.GroupVersionKind(), scheme.Scheme).(client.Object)
			obj.SetName(ref.Name)
			fmt.Println("try to delete", ref.GroupVersionKind(), ref.Name)
			if err := e.Config.InfraAPIClient.Delete(ctx, obj); err != nil {
				errList = append(errList, err)
			}
		}

		pc := v1beta1.ProviderConfig{
			ObjectMeta: metav1.ObjectMeta{
				Name: data.ProviderConfigName(ident),
			},
		}
		if err := e.Config.InfraAPIClient.Delete(ctx, &pc); err != nil {
			return err
		}

		fmt.Printf("Skip waiting for e2e provider config %s being deleted, lambda func takes too long time to be fully deleted\n", pc.Name)
	}

	if len(errList) != 0 {
		fmt.Println("failed to cleanup:", errList)
		return fmt.Errorf("failed to cleanup: %v", errList)
	}
	return nil
}

func newClient(configPath, context string) (client.Client, error) {
	kubeconfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath},
		&clientcmd.ConfigOverrides{CurrentContext: context}).ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("can't parse kubeconfig: %w", err)
	}
	c, err := client.New(kubeconfig, client.Options{
		Scheme: scheme.Scheme,
	})
	if err != nil {
		return nil, fmt.Errorf("can't new client: %w", err)
	}
	return c, nil
}
