package service

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/errors"
	cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v3"
	"google.golang.org/api/container/v1"
	gkehub "google.golang.org/api/gkehub/v1beta"
	"google.golang.org/api/serviceusage/v1"
	"google.golang.org/grpc/codes"

	providergcp "github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
)

const gkePolicyControllerFeatureID = "policycontroller"

type GoogleGKEPolicyControlClient struct {
	containerSvc       *container.Service
	serviceUsageSvc    *serviceusage.Service
	gkeHubSvc          *gkehub.Service
	resourceManagerSvc *cloudresourcemanager.Service
}

func NewGoogleGKEPolicyControlClient(ctx context.Context) (*GoogleGKEPolicyControlClient, error) {
	containerSvc, err := container.NewService(ctx)
	if err != nil {
		return nil, err
	}
	serviceUsageSvc, err := serviceusage.NewService(ctx)
	if err != nil {
		return nil, err
	}
	gkeHubSvc, err := gkehub.NewService(ctx)
	if err != nil {
		return nil, err
	}
	resourceManagerSvc, err := cloudresourcemanager.NewService(ctx)
	if err != nil {
		return nil, err
	}
	return &GoogleGKEPolicyControlClient{
		containerSvc:       containerSvc,
		serviceUsageSvc:    serviceUsageSvc,
		gkeHubSvc:          gkeHubSvc,
		resourceManagerSvc: resourceManagerSvc,
	}, nil
}

func (c *GoogleGKEPolicyControlClient) GetProjectIdentity(ctx context.Context, project string) (GKEPolicyControlProjectIdentity, error) {
	input := providergcp.CanonicalProjectIdentifier(project)
	if input == "" {
		return GKEPolicyControlProjectIdentity{}, errors.New("project is empty")
	}
	if c.resourceManagerSvc == nil {
		return GKEPolicyControlProjectIdentity{}, errors.New("resource manager service is not configured")
	}
	current, err := c.resourceManagerSvc.Projects.Get("projects/" + input).Context(ctx).Do()
	if err != nil {
		return GKEPolicyControlProjectIdentity{}, googleAPIStatusErrorf(err, codes.Internal, "get project %s", input)
	}
	if current == nil {
		return GKEPolicyControlProjectIdentity{}, newGRPCCodeErrorf(codes.Internal, "get project %s returned nil response", input)
	}
	return GKEPolicyControlProjectIdentity{
		Input:         input,
		ProjectID:     current.ProjectId,
		ProjectNumber: strings.TrimPrefix(current.Name, "projects/"),
	}, nil
}

func (c *GoogleGKEPolicyControlClient) GetServicesStatus(ctx context.Context, projectID string, services []string) ([]string, []string, error) {
	enabled := make([]string, 0, len(services))
	missing := make([]string, 0, len(services))
	for _, serviceName := range services {
		resourceName := fmt.Sprintf("projects/%s/services/%s", projectID, serviceName)
		svc, err := c.serviceUsageSvc.Services.Get(resourceName).Context(ctx).Do()
		if err != nil {
			if googleAPIStatusCode(err) == 404 {
				missing = append(missing, serviceName)
				continue
			}
			return enabled, missing, googleAPIStatusErrorf(err, codes.Internal, "get service %s", serviceName)
		}
		if svc != nil && strings.EqualFold(svc.State, "ENABLED") {
			enabled = append(enabled, serviceName)
		} else {
			missing = append(missing, serviceName)
		}
	}
	return enabled, missing, nil
}

func (c *GoogleGKEPolicyControlClient) StartEnableServices(ctx context.Context, projectID string, services []string) ([]string, error) {
	ops := []string{}
	for _, serviceName := range services {
		resourceName := fmt.Sprintf("projects/%s/services/%s", projectID, serviceName)
		op, err := c.serviceUsageSvc.Services.Enable(resourceName, &serviceusage.EnableServiceRequest{}).Context(ctx).Do()
		if err != nil {
			if googleAPIOperationInProgress(err) {
				continue
			}
			if googleAPIStatusCode(err) == 409 {
				enabled, getErr := c.serviceEnabled(ctx, resourceName)
				if getErr != nil {
					return ops, googleAPIGKEPolicyControlIAMStatusErrorf(getErr, "get service %s after enable conflict", serviceName)
				}
				if enabled {
					continue
				}
				return ops, googleAPIGKEPolicyControlIAMStatusErrorf(err, "enable service %s conflicted and service is not enabled", serviceName)
			}
			return ops, googleAPIGKEPolicyControlIAMStatusErrorf(err, "enable service %s", serviceName)
		}
		if op != nil && op.Name != "" {
			ops = append(ops, op.Name)
		}
	}
	return ops, nil
}

func (c *GoogleGKEPolicyControlClient) GetFleetMembership(ctx context.Context, projectID, location, clusterName string) (string, error) {
	clusterPath := gkeClusterPath(projectID, location, clusterName)
	cluster, err := c.containerSvc.Projects.Locations.Clusters.Get(clusterPath).Context(ctx).Do()
	if err != nil {
		return "", googleAPIStatusErrorf(err, codes.Internal, "get gke cluster %s", clusterPath)
	}
	if cluster == nil {
		return "", newGRPCCodeErrorf(codes.Internal, "get gke cluster %s returned nil response", clusterPath)
	}
	return normalizedFleetMembership(cluster.Fleet), nil
}

func (c *GoogleGKEPolicyControlClient) StartEnableFleet(ctx context.Context, projectID, location, clusterName, fleetProjectID string) ([]string, error) {
	clusterPath := gkeClusterPath(projectID, location, clusterName)
	op, err := c.containerSvc.Projects.Locations.Clusters.Update(clusterPath, &container.UpdateClusterRequest{
		Update: &container.ClusterUpdate{
			DesiredFleet: &container.Fleet{Project: fleetProjectID},
		},
	}).Context(ctx).Do()
	if err != nil {
		if googleAPIOperationInProgress(err) {
			return nil, nil
		}
		if googleAPIStatusCode(err) == 409 {
			membership, getErr := c.GetFleetMembership(ctx, projectID, location, clusterName)
			if getErr != nil {
				return nil, getErr
			}
			matches, matchErr := gkePolicyControlMembershipMatchesFleetProject(ctx, c, membership, fleetProjectID)
			if matchErr != nil {
				return nil, wrapGRPCCodeError(codes.FailedPrecondition, matchErr, "verify gke cluster fleet membership %q after enable conflict", membership)
			}
			if matches {
				return nil, nil
			}
			return nil, googleAPIGKEPolicyControlIAMStatusErrorf(err, "enable fleet for gke cluster %s conflicted and current membership %q does not match fleet project %s", clusterPath, membership, fleetProjectID)
		}
		return nil, googleAPIGKEPolicyControlIAMStatusErrorf(err, "enable fleet for gke cluster %s", clusterPath)
	}
	if op != nil && op.Name != "" {
		operationName := op.Name
		if !strings.HasPrefix(operationName, "projects/") {
			operationName = fmt.Sprintf("projects/%s/locations/%s/operations/%s", projectID, location, operationName)
		}
		return []string{operationName}, nil
	}
	return nil, nil
}

func (c *GoogleGKEPolicyControlClient) GetPolicyController(ctx context.Context, fleetProjectID, membership string) (GKEPolicyControllerStatus, error) {
	featurePath := gkePolicyControllerFeaturePath(fleetProjectID)
	out := GKEPolicyControllerStatus{FeaturePath: featurePath}
	feature, err := c.gkeHubSvc.Projects.Locations.Features.Get(featurePath).Context(ctx).Do()
	if err != nil {
		if googleAPIStatusCode(err) == 404 {
			return out, nil
		}
		return GKEPolicyControllerStatus{}, googleAPIStatusErrorf(err, codes.Internal, "get policycontroller feature %s", featurePath)
	}
	if feature == nil {
		return GKEPolicyControllerStatus{}, newGRPCCodeErrorf(codes.Internal, "get policycontroller feature %s returned nil response", featurePath)
	}
	spec, _, _, err := gkePolicyControllerMembershipWithKey(ctx, c, feature.MembershipSpecs, membership)
	if err != nil {
		return GKEPolicyControllerStatus{}, err
	}
	out.DesiredEnabled, out.MutationEnabled = gkePolicyControllerMembershipEnabled(spec)
	if state, _, ok, err := gkePolicyControllerMembershipWithKey(ctx, c, feature.MembershipStates, membership); err != nil {
		return GKEPolicyControllerStatus{}, err
	} else if ok && state.Policycontroller != nil {
		out.LifecycleState = strings.TrimSpace(state.Policycontroller.State)
		if mutationState, ok := state.Policycontroller.ComponentStates["mutation"]; ok {
			out.MutationComponentState = strings.TrimSpace(mutationState.State)
		}
	}
	return out, nil
}

func (c *GoogleGKEPolicyControlClient) StartEnablePolicyController(ctx context.Context, fleetProjectID, membership string) (string, []string, error) {
	featurePath := gkePolicyControllerFeaturePath(fleetProjectID)
	feature, ops, done, err := c.ensurePolicyControllerFeature(ctx, fleetProjectID, membership, featurePath)
	if err != nil {
		return "", ops, err
	}
	if done {
		return featurePath, ops, nil
	}
	return c.enablePolicyControllerMembership(ctx, featurePath, feature, membership, ops)
}

func (c *GoogleGKEPolicyControlClient) ensurePolicyControllerFeature(ctx context.Context, fleetProjectID, membership, featurePath string) (*gkehub.Feature, []string, bool, error) {
	feature, err := c.gkeHubSvc.Projects.Locations.Features.Get(featurePath).Context(ctx).Do()
	if err == nil {
		return feature, []string{}, false, nil
	}
	if googleAPIStatusCode(err) != 404 {
		return nil, nil, false, googleAPIGKEPolicyControlIAMStatusErrorf(err, "get policycontroller feature %s", featurePath)
	}

	ops := []string{}
	op, createErr := c.gkeHubSvc.Projects.Locations.Features.Create(
		gkeFeatureParent(fleetProjectID),
		&gkehub.Feature{MembershipSpecs: gkePolicyControllerInitialMembershipSpec(membership)},
	).FeatureId(gkePolicyControllerFeatureID).Context(ctx).Do()
	if createErr == nil {
		return nil, appendGKEHubOperationName(ops, op), true, nil
	}

	createInProgress := googleAPIOperationInProgress(createErr)
	if !createInProgress && googleAPIStatusCode(createErr) != 409 {
		return nil, nil, false, googleAPIGKEPolicyControlIAMStatusErrorf(createErr, "create policycontroller feature %s", featurePath)
	}
	feature, err = c.gkeHubSvc.Projects.Locations.Features.Get(featurePath).Context(ctx).Do()
	if err == nil {
		return feature, ops, false, nil
	}
	if googleAPIStatusCode(err) == 404 {
		if !createInProgress {
			return nil, ops, false, googleAPIGKEPolicyControlIAMStatusErrorf(createErr, "create policycontroller feature %s conflicted and feature is not available", featurePath)
		}
		return nil, ops, true, nil
	}
	return nil, ops, false, googleAPIGKEPolicyControlIAMStatusErrorf(err, "get policycontroller feature %s after create conflict", featurePath)
}

func (c *GoogleGKEPolicyControlClient) enablePolicyControllerMembership(ctx context.Context, featurePath string, feature *gkehub.Feature, membership string, ops []string) (string, []string, error) {
	spec, alreadyEnabled, err := gkePolicyControllerEnableSpec(ctx, c, feature, membership)
	if err != nil {
		return "", ops, err
	}
	if alreadyEnabled {
		return featurePath, ops, nil
	}

	op, err := c.patchPolicyControllerFeature(ctx, featurePath, spec)
	if err != nil {
		if googleAPIOperationInProgress(err) {
			return featurePath, ops, nil
		}
		if googleAPIStatusCode(err) == 409 {
			op, err = c.retryPatchPolicyControllerFeatureAfterConflict(ctx, featurePath, membership)
			if err != nil {
				return "", ops, err
			}
			return featurePath, appendGKEHubOperationName(ops, op), nil
		}
		return "", ops, googleAPIGKEPolicyControlIAMStatusErrorf(err, "update policycontroller feature %s", featurePath)
	}
	return featurePath, appendGKEHubOperationName(ops, op), nil
}

func (c *GoogleGKEPolicyControlClient) serviceEnabled(ctx context.Context, resourceName string) (bool, error) {
	svc, err := c.serviceUsageSvc.Services.Get(resourceName).Context(ctx).Do()
	if err != nil {
		if googleAPIStatusCode(err) == 404 {
			return false, nil
		}
		return false, err
	}
	return svc != nil && strings.EqualFold(svc.State, "ENABLED"), nil
}

func gkePolicyControlMembershipMatchesFleetProject(ctx context.Context, cli GKEPolicyControlClient, membership, fleetProjectID string) (bool, error) {
	projectID, _, _, ok := parseGKEMembershipPath(membership)
	if !ok {
		return false, nil
	}
	return gkePolicyControlProjectsEqual(ctx, cli, projectID, fleetProjectID)
}

func (c *GoogleGKEPolicyControlClient) patchPolicyControllerFeature(ctx context.Context, featurePath string, spec map[string]gkehub.MembershipFeatureSpec) (*gkehub.Operation, error) {
	return c.gkeHubSvc.Projects.Locations.Features.Patch(
		featurePath,
		&gkehub.Feature{MembershipSpecs: spec},
	).UpdateMask("membership_specs").Context(ctx).Do()
}

func (c *GoogleGKEPolicyControlClient) retryPatchPolicyControllerFeatureAfterConflict(ctx context.Context, featurePath, membership string) (*gkehub.Operation, error) {
	feature, err := c.gkeHubSvc.Projects.Locations.Features.Get(featurePath).Context(ctx).Do()
	if err != nil {
		return nil, googleAPIGKEPolicyControlIAMStatusErrorf(err, "get policycontroller feature %s after update conflict", featurePath)
	}
	spec, alreadyEnabled, err := gkePolicyControllerEnableSpec(ctx, c, feature, membership)
	if err != nil {
		return nil, err
	}
	if alreadyEnabled {
		return nil, nil
	}
	op, err := c.patchPolicyControllerFeature(ctx, featurePath, spec)
	if err != nil {
		if googleAPIOperationInProgress(err) {
			return nil, nil
		}
		return nil, googleAPIGKEPolicyControlIAMStatusErrorf(err, "retry update policycontroller feature %s after conflict", featurePath)
	}
	return op, nil
}

func gkePolicyControllerInitialMembershipSpec(membership string) map[string]gkehub.MembershipFeatureSpec {
	return map[string]gkehub.MembershipFeatureSpec{
		membership: enableGKEPolicyControllerMembershipSpec(gkehub.MembershipFeatureSpec{}),
	}
}

func appendGKEHubOperationName(ops []string, op *gkehub.Operation) []string {
	if op != nil && op.Name != "" {
		return append(ops, op.Name)
	}
	return ops
}

func gkePolicyControllerEnableSpec(ctx context.Context, cli GKEPolicyControlClient, feature *gkehub.Feature, membership string) (map[string]gkehub.MembershipFeatureSpec, bool, error) {
	if feature == nil {
		return gkePolicyControllerInitialMembershipSpec(membership), false, nil
	}

	membershipKey := membership
	if _, key, ok, err := gkePolicyControllerMembershipWithKey(ctx, cli, feature.MembershipSpecs, membership); err != nil {
		return nil, false, err
	} else if ok {
		membershipKey = key
	}
	installEnabled, mutationEnabled := gkePolicyControllerMembershipEnabled(feature.MembershipSpecs[membershipKey])
	if installEnabled && mutationEnabled {
		return nil, true, nil
	}

	spec := map[string]gkehub.MembershipFeatureSpec{}
	for key, value := range feature.MembershipSpecs {
		spec[key] = value
	}
	spec[membershipKey] = enableGKEPolicyControllerMembershipSpec(spec[membershipKey])
	return spec, false, nil
}

func enableGKEPolicyControllerMembershipSpec(spec gkehub.MembershipFeatureSpec) gkehub.MembershipFeatureSpec {
	if spec.Policycontroller == nil {
		spec.Policycontroller = &gkehub.PolicyControllerMembershipSpec{}
	}
	if spec.Policycontroller.PolicyControllerHubConfig == nil {
		spec.Policycontroller.PolicyControllerHubConfig = &gkehub.PolicyControllerHubConfig{}
	}
	spec.Policycontroller.PolicyControllerHubConfig.InstallSpec = "INSTALL_SPEC_ENABLED"
	spec.Policycontroller.PolicyControllerHubConfig.MutationEnabled = true
	return spec
}

func gkePolicyControllerMembershipWithKey[T any](ctx context.Context, cli GKEPolicyControlClient, memberships map[string]T, membership string) (T, string, bool, error) {
	if value, ok := memberships[membership]; ok {
		return value, membership, true, nil
	}
	projectID, location, membershipID, ok := parseGKEMembershipPath(membership)
	if !ok {
		var zero T
		return zero, "", false, nil
	}
	var match string
	for candidate := range memberships {
		candidateProjectID, candidateLocation, candidateMembershipID, candidateOK := parseGKEMembershipPath(candidate)
		if !candidateOK ||
			candidateLocation != location ||
			candidateMembershipID != membershipID {
			continue
		}
		matches, err := gkePolicyControlProjectsEqual(ctx, cli, candidateProjectID, projectID)
		if err != nil {
			var zero T
			return zero, "", false, wrapGRPCCodeError(codes.FailedPrecondition, err, "gke policycontroller membership key %q cannot be verified against %q", candidate, membership)
		}
		if !matches {
			continue
		}
		if match != "" {
			var zero T
			return zero, "", false, newGRPCCodeErrorf(codes.FailedPrecondition, "gke policycontroller membership key for %q is ambiguous", membership)
		}
		match = candidate
	}
	if match == "" {
		var zero T
		return zero, "", false, nil
	}
	return memberships[match], match, true, nil
}

func gkePolicyControllerMembershipEnabled(spec gkehub.MembershipFeatureSpec) (installEnabled, mutationEnabled bool) {
	if spec.Policycontroller == nil || spec.Policycontroller.PolicyControllerHubConfig == nil {
		return false, false
	}
	hubConfig := spec.Policycontroller.PolicyControllerHubConfig
	return hubConfig.InstallSpec == "INSTALL_SPEC_ENABLED", hubConfig.MutationEnabled
}

func gkeClusterPath(projectID, location, clusterName string) string {
	return fmt.Sprintf("projects/%s/locations/%s/clusters/%s", projectID, location, clusterName)
}

func gkeFeatureParent(projectID string) string {
	return fmt.Sprintf("projects/%s/locations/global", projectID)
}

func gkePolicyControllerFeaturePath(projectID string) string {
	return fmt.Sprintf("%s/features/%s", gkeFeatureParent(projectID), gkePolicyControllerFeatureID)
}

func normalizedFleetMembership(fleet *container.Fleet) string {
	if fleet == nil || strings.TrimSpace(fleet.Membership) == "" {
		return ""
	}
	membership := strings.TrimSpace(fleet.Membership)
	membership = strings.TrimPrefix(membership, "//gkehub.googleapis.com/")
	return membership
}
