package service

import (
	"fmt"
	"strings"

	"github.com/pingcap/errors"
	"google.golang.org/api/googleapi"
	"google.golang.org/grpc/codes"
)

const (
	gkeClusterAlreadyHasOperation      = "CLUSTER_ALREADY_HAS_OPERATION"
	googleAPIOperationInProgressReason = "OPERATION_IN_PROGRESS"
)

func googleAPIOperationInProgress(err error) bool {
	e, ok := errors.Cause(err).(*googleapi.Error)
	if !ok || (e.Code != 400 && e.Code != 409) {
		return false
	}
	if googleAPIErrorHasAnyReason(e, gkeClusterAlreadyHasOperation, googleAPIOperationInProgressReason) {
		return true
	}
	return googleAPIMessageLooksOperationInProgress(e.Message)
}

func googleAPIMessageLooksOperationInProgress(message string) bool {
	msg := strings.ToLower(message)
	if !strings.Contains(msg, "operation") {
		return false
	}
	for _, phrase := range []string{
		"in progress",
		"currently running",
		"incompatible operation",
		"already has operation",
		"already has an operation",
		"already an operation",
		"operation is already running",
		"operation already running",
	} {
		if strings.Contains(msg, phrase) {
			return true
		}
	}
	return false
}

func googleAPIErrorHasAnyReason(e *googleapi.Error, reasons ...string) bool {
	for _, reason := range reasons {
		if googleAPIErrorHasReason(e, reason) {
			return true
		}
	}
	return false
}

func googleAPIErrorHasReason(e *googleapi.Error, reason string) bool {
	if e == nil {
		return false
	}
	for _, item := range e.Errors {
		if strings.EqualFold(item.Reason, reason) {
			return true
		}
	}
	for _, detail := range e.Details {
		if typed, ok := detail.(map[string]interface{}); ok && strings.EqualFold(fmt.Sprint(typed["reason"]), reason) {
			return true
		}
	}
	return false
}

func googleAPIStatusCode(err error) int {
	if e, ok := errors.Cause(err).(*googleapi.Error); ok {
		return e.Code
	}
	return 0
}

func googleAPIStatusErrorf(err error, fallback codes.Code, format string, args ...interface{}) error {
	return wrapGRPCCodeError(googleAPIStatusToGRPCCode(err, fallback), err, format, args...)
}

func googleAPIGKEPolicyControlIAMStatusErrorf(err error, format string, args ...interface{}) error {
	if googleAPIStatusCode(err) == 403 {
		return wrapGRPCCodeError(codes.PermissionDenied, err, format, args...)
	}
	return googleAPIStatusErrorf(err, codes.Internal, format, args...)
}

func googleAPIStatusToGRPCCode(err error, fallback codes.Code) codes.Code {
	switch googleAPIStatusCode(err) {
	case 400:
		return codes.InvalidArgument
	case 401:
		return codes.Unauthenticated
	case 403:
		return codes.PermissionDenied
	case 404:
		return codes.NotFound
	case 409:
		return codes.AlreadyExists
	case 429:
		return codes.ResourceExhausted
	case 500, 502, 503, 504:
		return codes.Unavailable
	default:
		return fallback
	}
}
