package utils

import (
	"encoding/binary"
	"fmt"
	"net"
	"strings"

	"github.com/pkg/errors"
	infra "github.com/tidbcloud/tidb-management-service/api/serverless_infra"
)

const (
	GatewayTokenLen = 15
)

//nolint:lll // nolint
var (
	DOC_LINK_USER_TOKEN                 = " See https://docs.pingcap.com/tidbcloud/select-cluster-tier#user-name-prefix"
	DOC_LINK_TLS_CONNETION              = " See https://docs.pingcap.com/tidbcloud/secure-connections-to-serverless-tier-clusters"
	DOC_LINK_ENDPOINT_SETTING           = " See https://docs.pingcap.com/tidbcloud/connect-via-standard-connection-serverless#disable-a-public-endpoint"
	ERR_CLUSTER_NOT_FOUND               = "Access denied. Please check your user name and password." + DOC_LINK_USER_TOKEN
	ERR_CLUSTER_TEMPORARY_UNAVAILABLE   = "Your cluster is temporarily unavailable. Please try again later."
	ERR_CLUSTER_RESOURCE_INIT           = "Your cluster is in the middle of initializing. Please try again later."
	ERR_TLS_ONLY                        = "Connections using insecure transport are prohibited." + DOC_LINK_TLS_CONNETION
	ERR_USER_MISSING_TOKEN              = "Missing user name prefix." + DOC_LINK_USER_TOKEN
	ERR_CLUSTER_RESTORING               = "Your cluster is in the middle of restoring. Please try again after the restore is complete."
	ERR_FORBIDDEN_USER                  = "Access denied for user '%s'."
	ERR_PUBLIC_ENDPOINT_DISABLE         = "Public endpoint is disabled on your cluster '%s'." + DOC_LINK_ENDPOINT_SETTING
	ERR_PRIVATELINK_ENDPOINT_DISABLE    = "Private endpoint is disabled on your cluster '%s'."
	ERR_PRIVATELINK_ENDPOINT_ZR_DISABLE = "Private endpoint ZR is disabled on your cluster '%s'."
	ERR_PRIVATELINK_ENDPOINT_BLOCKED    = "VPC endpoint '%s' is not allowed to connect to this cluster '%s' via PrivateLink."
	ERR_CLUSTER_BE_THROTTLED            = "Due to the usage quota being exhausted, access to the cluster '%s' has been restricted. Try increasing spending limits to gain full access. For more information, see https://docs.pingcap.com/tidbcloud/serverless-limitations#usage-quota"
	ERR_BRANCH_NOT_RUNNING              = "Your branch is in the process of %s. Please try again later."
	ERR_CLUSTER_BLOCK_BY_TRAFFIC_FILTER = "Host '%s' is not allowed to connect to your cluster '%s'. See https://docs.pingcap.com/tidbcloud/configure-serverless-firewall-rules-for-public-endpoints"
	ERR_CONNECTION_LIMIT_EXCEEDED       = "Connection limit exceeded. Please reduce your active connections or upgrade your cluster tier to increase capacity."
)

var (
	GATEWAY_SESSION_PHASE_CONNECT_ACCEPT  = "accept"
	GATEWAY_SESSION_PHASE_TOKEN_PARSER    = "parse_token"
	GATEWAY_SESSION_PHASE_GET_CLUSTER     = "get_cluster"
	GATEWAY_SESSION_PHASE_CONNECT_BACKEND = "connect_backend"
	GATEWAY_SESSION_PHASE_RELAY_DATA      = "relay_data"
	GATEWAY_SESSION_PHASE_CONNECT_CLOSE   = "close"

	GATEWAY_SESSION_STATUS_SUCCESS = "success"
	GATEWAY_SESSION_STATUS_FAILED  = "failed"
)

type ExternalError struct {
	msg   string
	cause []error
}

func (e *ExternalError) Error() string {
	return e.msg
}

func (e *ExternalError) Cause() error {
	cl := make([]string, 0, len(e.cause))
	for _, c := range e.cause {
		if ce := errors.Cause(c); ce != nil {
			cl = append(cl, ce.Error())
		}
	}
	if len(cl) == 0 {
		return errors.New(e.msg)
	}
	return errors.New(strings.Join(cl, ";"))
}

func NewExternalError(msg string, cause ...error) error {
	return &ExternalError{msg: msg, cause: cause}
}

type InternalError struct {
	msg   string
	cause []error
}

func (e *InternalError) Error() string {
	return e.msg
}

func (e *InternalError) Cause() error {
	cl := make([]string, 0, len(e.cause))
	for _, c := range e.cause {
		if ce := errors.Cause(c); ce != nil {
			cl = append(cl, ce.Error())
		}
	}
	if len(cl) == 0 {
		return errors.New(e.msg)
	}
	return errors.New(strings.Join(cl, ";"))
}

func NewInternalError(msg string, cause ...error) error {
	return &InternalError{msg: msg, cause: cause}
}

func ValidAuthorizedNetworks(networks []infra.AuthorizedNetwork, clientIP string) bool {
	/* downgrade strategy start, allowd all */
	if networks == nil {
		return true
	}
	/* downgrade strategy end. */

	cip, err := ipToUint32(clientIP)
	if err != nil || cip == 0 {
		return true
	}
	for _, n := range networks {
		sip, err := ipToUint32(n.StartIpAddress)
		if err != nil {
			continue
		}
		eip, err := ipToUint32(n.EndIpAddress)
		if err != nil {
			continue
		}
		if sip <= cip && cip <= eip {
			return true
		}
		/* by design: 169.254.65.87 represents all AWS networks */
		if sip == 2852012375 && eip == 2852012375 && AwsIPSvc.Contains(clientIP) {
			return true
		}
	}
	return false
}

func ValidPrivatelinkAuthorizedNetworks(networks []infra.PrivatelinkAuthorizedNetwork, vpcEndpointID string) bool {
	if len(networks) == 0 {
		return true
	}
	// allow connection from tools like dm.
	if vpcEndpointID == "" {
		return true
	}
	for _, network := range networks {
		if network.VpcEndpointId == "*" || network.VpcEndpointId == vpcEndpointID {
			return true
		}
	}
	return false
}

func ipToUint32(ipStr string) (uint32, error) {
	ip := net.ParseIP(ipStr)
	if ip == nil || ip.To4() == nil {
		return 0, fmt.Errorf("invalid IPv4 address %s", ipStr)
	}
	ipTo4 := ip.To4()
	return binary.BigEndian.Uint32(ipTo4), nil
}
