package util

import (
	"context"
	"fmt"
	"hash"
	"hash/fnv"
	"reflect"
	"runtime"
	"strings"

	ec2api "github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/davecgh/go-spew/spew"
	ctrl "sigs.k8s.io/controller-runtime"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/providers/aws"
	"github.com/tidbcloud/infra-api/pkg/providers/gcp"
)

var setupLog = ctrl.Log.WithName("setup")

func FnvHash(raw string) string {
	hash := fnv.New32()
	// fnv.New32().Write() never return error
	_, _ = hash.Write([]byte(raw))
	return fmt.Sprintf("%x", hash.Sum32())
}

// DeepHashObject is copied from "k8s.io/kubernetes/pkg/util/hash"
// it writes specified object to hash using the spew library
// which follows pointers and prints actual values of the nested objects
// ensuring the hash does not change when a pointer changes.
func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
	hasher.Reset()
	printer := spew.ConfigState{
		Indent:         " ",
		SortKeys:       true,
		DisableMethods: true,
		SpewKeys:       true,
	}
	printer.Fprintf(hasher, "%#v", objectToWrite)
}

type CloudPeerCreator struct {
	awsClient aws.Client
	gcpClient gcp.GCPAPI
}

func NewCloudPeerCreator(a aws.Client, g gcp.GCPAPI) *CloudPeerCreator {
	return &CloudPeerCreator{
		awsClient: a,
		gcpClient: g,
	}
}

func (c *CloudPeerCreator) Create(peer *cloudv1.NetworkPeer, sn *cloudv1.Network) error {
	if peer.Spec.Target.AWS != nil {
		sourceNetworkStatus, err := cloudv1.GardenerNetworkStatusFromNetwork(sn)
		if err != nil {
			return err
		}

		peeringInput := &ec2api.CreateVpcPeeringConnectionInput{
			PeerOwnerId: &peer.Spec.Target.AWS.AccountID,
			PeerRegion:  &peer.Spec.Target.AWS.PeerRegion,
			PeerVpcId:   &peer.Spec.Target.AWS.VpcID,
			VpcId:       sourceNetworkStatus.AWS.VpcID,
			// tags were not specified as a known issue: https://jira.tidbcloud.com/browse/DM-3118
		}

		res, err := c.awsClient.Ec2().CreateVpcPeeringConnection(context.TODO(), peeringInput, aws.WithEC2Region(sn.Spec.Region))
		if err != nil {
			return err
		}
		vpcPeeringID := res.VpcPeeringConnection.VpcPeeringConnectionId
		setupLog.Info("create VPC peering", "vpcPeeringID", *vpcPeeringID)
	} else if peer.Spec.Target.GCP != nil {
		sourceNetworkStatus, err := cloudv1.GardenerNetworkStatusFromNetwork(sn)
		if err != nil {
			return err
		}
		err = c.gcpClient.CreateVpcPeering(*sourceNetworkStatus.GCP.ProjectID, peer.Name, peer.Spec.Source,
			peer.Spec.Target.GCP.ProjectID, peer.Spec.Target.GCP.PeerNetwork)
		if err != nil {
			return err
		}

	}
	return nil
}

func GetFunctionName(f interface{}) (name string) {
	v := reflect.ValueOf(f)
	function := runtime.FuncForPC(v.Pointer())
	if function == nil {
		return ""
	}
	strs := strings.Split(function.Name(), ".")
	if len(strs) < 1 {
		return ""
	}
	return strs[len(strs)-1]
}
