package internet

import (
	"context"
	"fmt"

	"github.com/pingcap/log"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/message"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/internet"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type InternetService struct {
	internetrw internet.InternetReaderWriter
	vpcr       vpc.VPCReader
	config     rsconfig.RootConfig
}

func NewInternetService(
	internetrw internet.InternetReaderWriter,
	vpcr vpc.VPCReader,
	config rsconfig.RootConfig) *InternetService {
	return &InternetService{
		internetrw: internetrw,
		vpcr:       vpcr,
		config:     config,
	}
}

func (s *InternetService) CreateInternet(ctx context.Context, req *message.CreateInternetReq) (*v1alpha1.Internet, error) {
	vpcList := &v1alpha1.VPCList{}
	if err := s.vpcr.List(ctx, vpcList, client.MatchingLabels{
		v1alpha1.VPCIDKey: req.VPCID,
	}); err != nil {
		log.Error("failed to list vpc", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpc")
	}

	if len(vpcList.Items) == 0 {
		log.Info("vpc not found")
		return nil, bizerr.New(bizerr.ResourceNotFound, "vpc not found")
	}
	if len(vpcList.Items) > 1 {
		log.Info("multiple vpc found")
		return nil, bizerr.New(bizerr.InternalServerError, "multiple vpc found")
	}

	internet, err := s.ensureInternet(ctx, &vpcList.Items[0], req)
	if err != nil {
		return nil, err
	}

	return internet, nil
}

func (s *InternetService) ensureInternet(ctx context.Context, vpc *v1alpha1.VPC, req *message.CreateInternetReq) (*v1alpha1.Internet, error) {
	internetlist := &v1alpha1.InternetList{}
	if err := s.internetrw.List(ctx, internetlist, client.MatchingLabels{
		v1alpha1.VPCNameKey: vpc.Name,
	}); err != nil {
		log.Error("failed to list internet", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list internet")
	}
	if len(internetlist.Items) > 0 {
		internet := &internetlist.Items[0]
		for i, inner := range internetlist.Items {
			if utils.NameLargerThan(inner.Name, internet.Name) {
				internet = &internetlist.Items[i]
			}
		}
		switch internet.Status.Phase {
		//if ready to delete , modify status to ready
		case v1alpha1.InternetPhaseReadyForDelete:
			internet.Status.Phase = v1alpha1.InternetPhaseActive
			if err := s.internetrw.UpdateStatus(ctx, internet); err != nil {
				return nil, bizerr.New(bizerr.K8SClientError, "failed to update internet status")
			}
			return internet, bizerr.New(bizerr.ResourceConflict, "internet already exists")
		//if terminating , create a new internet
		case v1alpha1.InternetPhaseFailed, v1alpha1.InternetPhaseDeleted, v1alpha1.InternetPhaseDeleting:
			internet, err := s.createInternet(ctx, utils.InceareName(internet.Name), vpc, req)
			if err != nil {
				return nil, bizerr.New(bizerr.K8SClientError, "failed to create internet")
			}
			return internet, nil
		}
		return internet, bizerr.New(bizerr.ResourceConflict, "internet already exists")
	}
	name := fmt.Sprintf("%s-%d", vpc.Name, 0)
	internet, err := s.createInternet(ctx, name, vpc, req)
	if err != nil {
		return nil, err
	}
	return internet, nil
}

func (s *InternetService) createInternet(ctx context.Context, name string, vpc *v1alpha1.VPC, req *message.CreateInternetReq) (*v1alpha1.Internet, error) {
	namer := utils.NewNamer(req.TenantID, req.ProjectID, "", s.config.RegionConfig.Name, s.config.Env)
	internet := &v1alpha1.Internet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: vpc.Namespace,
			Labels: map[string]string{
				v1alpha1.VPCNameKey: vpc.Name,
				v1alpha1.RegionKey:  s.config.RegionConfig.Name,
				// tags for billing
				v1alpha1.TagProjectKey: namer.GetProjectID(),
				v1alpha1.TagTenantKey:  namer.GetTenantID(),
			},
		},
		Spec: v1alpha1.InternetSpec{
			Provider: s.config.Provider,
			Region:   s.config.RegionConfig.Name,
			VPCRef: &v1alpha1.Reference{
				Name:      vpc.Name,
				Namespace: vpc.Namespace,
			},
		},
	}
	if req.TenantID != "" {
		internet.Labels[utils.GenTenantLabelKey(req.TenantID)] = "true"
	}
	if req.ProjectID != "" {
		internet.Labels[utils.GenProjectLabelKey(req.ProjectID)] = "true"
	}
	internet.ObjectMeta.Labels = util.MergeMaps(internet.ObjectMeta.Labels, s.config.ResourceLabels)
	if err := s.internetrw.Create(ctx, internet); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to create internet")
	}
	return internet, nil
}

func (s *InternetService) DeleteInternet(ctx context.Context, req *message.DeleteInternetReq) error {
	internet := &v1alpha1.Internet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      req.Name,
			Namespace: req.Namespace,
		},
	}
	if err := s.internetrw.Delete(ctx, internet); err != nil {
		if apierrors.IsNotFound(err) {
			log.Info("internet not found", zap.String("name", req.Name))
			return bizerr.New(bizerr.ResourceNotFound, "internet not found")
		}
		log.Error("failed to delete internet", zap.Error(err))
		return bizerr.New(bizerr.K8SClientError, "failed to delete internet")
	}
	return nil
}

func (s *InternetService) GetInternet(ctx context.Context, req *message.GetInternetReq) (*v1alpha1.Internet, error) {
	internet := &v1alpha1.Internet{}
	if err := s.internetrw.Get(ctx, client.ObjectKey{
		Name:      req.Name,
		Namespace: req.Namespace,
	}, internet); err != nil {
		if client.IgnoreNotFound(err) != nil {
			return nil, bizerr.New(bizerr.K8SClientError, "failed to get internet")
		}
		return nil, bizerr.New(bizerr.ResourceNotFound, "internet not found")
	}
	return internet, nil
}
