package cloudflare

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/goccy/go-json"
)

// LoadBalancerPool represents a load balancer pool's properties.
type LoadBalancerPool struct {
	ID                string                      `json:"id,omitempty"`
	CreatedOn         *time.Time                  `json:"created_on,omitempty"`
	ModifiedOn        *time.Time                  `json:"modified_on,omitempty"`
	Description       string                      `json:"description"`
	Name              string                      `json:"name"`
	Enabled           bool                        `json:"enabled"`
	MinimumOrigins    *int                        `json:"minimum_origins,omitempty"`
	Monitor           string                      `json:"monitor,omitempty"`
	Origins           []LoadBalancerOrigin        `json:"origins"`
	NotificationEmail string                      `json:"notification_email,omitempty"`
	Latitude          *float32                    `json:"latitude,omitempty"`
	Longitude         *float32                    `json:"longitude,omitempty"`
	LoadShedding      *LoadBalancerLoadShedding   `json:"load_shedding,omitempty"`
	OriginSteering    *LoadBalancerOriginSteering `json:"origin_steering,omitempty"`
	Healthy           *bool                       `json:"healthy,omitempty"`

	// CheckRegions defines the geographic region(s) from where to run health-checks from - e.g. "WNAM", "WEU", "SAF", "SAM".
	// Providing a null/empty value means "all regions", which may not be available to all plan types.
	CheckRegions []string `json:"check_regions"`
}

// LoadBalancerOrigin represents a Load Balancer origin's properties.
type LoadBalancerOrigin struct {
	Name    string `json:"name"`
	Address string `json:"address"`
	Enabled bool   `json:"enabled"`
	// Weight of this origin relative to other origins in the pool.
	// Based on the configured weight the total traffic is distributed
	// among origins within the pool.
	//
	// When LoadBalancerOriginSteering.Policy="least_outstanding_requests", this
	// weight is used to scale the origin's outstanding requests.
	// When LoadBalancerOriginSteering.Policy="least_connections", this
	// weight is used to scale the origin's open connections.
	Weight float64             `json:"weight"`
	Header map[string][]string `json:"header"`
	// The virtual network subnet ID the origin belongs in.
	// Virtual network must also belong to the account.
	VirtualNetworkID string `json:"virtual_network_id,omitempty"`
}

// LoadBalancerOriginSteering controls origin selection for new sessions and traffic without session affinity.
type LoadBalancerOriginSteering struct {
	// Policy determines the type of origin steering policy to use.
	// It defaults to "random" (weighted) when empty or unspecified.
	//
	// "random": Select an origin randomly.
	//
	// "hash": Select an origin by computing a hash over the CF-Connecting-IP address.
	//
	// "least_outstanding_requests": Select an origin by taking into consideration origin weights,
	// as well as each origin's number of outstanding requests. Origins with more pending requests
	// are weighted proportionately less relative to others.
	//
	// "least_connections": Select an origin by taking into consideration origin weights,
	// as well as each origin's number of open connections. Origins with more open connections
	// are weighted proportionately less relative to others. Supported for HTTP/1 and HTTP/2 connections.
	Policy string `json:"policy,omitempty"`
}

// LoadBalancerMonitor represents a load balancer monitor's properties.
type LoadBalancerMonitor struct {
	ID              string              `json:"id,omitempty"`
	CreatedOn       *time.Time          `json:"created_on,omitempty"`
	ModifiedOn      *time.Time          `json:"modified_on,omitempty"`
	Type            string              `json:"type"`
	Description     string              `json:"description"`
	Method          string              `json:"method"`
	Path            string              `json:"path"`
	Header          map[string][]string `json:"header"`
	Timeout         int                 `json:"timeout"`
	Retries         int                 `json:"retries"`
	Interval        int                 `json:"interval"`
	ConsecutiveUp   int                 `json:"consecutive_up"`
	ConsecutiveDown int                 `json:"consecutive_down"`
	Port            uint16              `json:"port,omitempty"`
	ExpectedBody    string              `json:"expected_body"`
	ExpectedCodes   string              `json:"expected_codes"`
	FollowRedirects bool                `json:"follow_redirects"`
	AllowInsecure   bool                `json:"allow_insecure"`
	ProbeZone       string              `json:"probe_zone"`
}

// LoadBalancer represents a load balancer's properties.
type LoadBalancer struct {
	ID                        string                     `json:"id,omitempty"`
	CreatedOn                 *time.Time                 `json:"created_on,omitempty"`
	ModifiedOn                *time.Time                 `json:"modified_on,omitempty"`
	Description               string                     `json:"description"`
	Name                      string                     `json:"name,omitempty"`
	TTL                       int                        `json:"ttl,omitempty"`
	FallbackPool              string                     `json:"fallback_pool"`
	DefaultPools              []string                   `json:"default_pools"`
	RegionPools               map[string][]string        `json:"region_pools"`
	PopPools                  map[string][]string        `json:"pop_pools"`
	CountryPools              map[string][]string        `json:"country_pools"`
	Proxied                   bool                       `json:"proxied"`
	Enabled                   *bool                      `json:"enabled,omitempty"`
	Persistence               string                     `json:"session_affinity,omitempty"`
	PersistenceTTL            int                        `json:"session_affinity_ttl,omitempty"`
	SessionAffinityAttributes *SessionAffinityAttributes `json:"session_affinity_attributes,omitempty"`
	Rules                     []*LoadBalancerRule        `json:"rules,omitempty"`
	RandomSteering            *RandomSteering            `json:"random_steering,omitempty"`
	AdaptiveRouting           *AdaptiveRouting           `json:"adaptive_routing,omitempty"`
	LocationStrategy          *LocationStrategy          `json:"location_strategy,omitempty"`

	// SteeringPolicy controls pool selection logic.
	//
	// "off": Select pools in DefaultPools order.
	//
	// "geo": Select pools based on RegionPools/PopPools/CountryPools.
	// For non-proxied requests, the country for CountryPools is determined by LocationStrategy.
	//
	// "dynamic_latency": Select pools based on RTT (requires health checks).
	//
	// "random": Selects pools in a random order.
	//
	// "proximity": Use the pools' latitude and longitude to select the closest pool using
	// the Cloudflare PoP location for proxied requests or the location determined by
	// LocationStrategy for non-proxied requests.
	//
	// "least_outstanding_requests": Select a pool by taking into consideration
	// RandomSteering weights, as well as each pool's number of outstanding requests.
	// Pools with more pending requests are weighted proportionately less relative to others.
	//
	// "least_connections": Select a pool by taking into consideration
	// RandomSteering weights, as well as each pool's number of open connections.
	// Pools with more open connections are weighted proportionately less relative to others.
	// Supported for HTTP/1 and HTTP/2 connections.
	//
	// "": Maps to "geo" if RegionPools or PopPools or CountryPools have entries otherwise "off".
	SteeringPolicy string `json:"steering_policy,omitempty"`

	// TunnelID is the ID of the tunnel associated with this load balancer.
	// It is only used for account load balancers and has no relation to any
	// tunnels used as origins for this load balancer.
	TunnelID string `json:"tunnel_id,omitempty"`
}

// LoadBalancerLoadShedding contains the settings for controlling load shedding.
type LoadBalancerLoadShedding struct {
	DefaultPercent float32 `json:"default_percent,omitempty"`
	DefaultPolicy  string  `json:"default_policy,omitempty"`
	SessionPercent float32 `json:"session_percent,omitempty"`
	SessionPolicy  string  `json:"session_policy,omitempty"`
}

// LoadBalancerRule represents a single rule entry for a Load Balancer. Each rules
// is run one after the other in priority order. Disabled rules are skipped.
type LoadBalancerRule struct {
	Overrides LoadBalancerRuleOverrides `json:"overrides"`

	// Name is required but is only used for human readability
	Name string `json:"name"`

	Condition string `json:"condition"`

	// Priority controls the order of rule execution the lowest value will be invoked first
	Priority int `json:"priority"`

	// FixedResponse if set and the condition is true we will not run
	// routing logic but rather directly respond with the provided fields.
	// FixedResponse implies terminates.
	FixedResponse *LoadBalancerFixedResponseData `json:"fixed_response,omitempty"`

	Disabled bool `json:"disabled"`

	// Terminates flag this rule as 'terminating'. No further rules will
	// be executed after this one.
	Terminates bool `json:"terminates,omitempty"`
}

// LoadBalancerFixedResponseData contains all the data needed to generate
// a fixed response from a Load Balancer. This behavior can be enabled via Rules.
type LoadBalancerFixedResponseData struct {
	// MessageBody data to write into the http body
	MessageBody string `json:"message_body,omitempty"`
	// StatusCode the http status code to response with
	StatusCode int `json:"status_code,omitempty"`
	// ContentType value of the http 'content-type' header
	ContentType string `json:"content_type,omitempty"`
	// Location value of the http 'location' header
	Location string `json:"location,omitempty"`
}

// LoadBalancerRuleOverrides are the set of field overridable by the rules system.
type LoadBalancerRuleOverrides struct {
	// session affinity
	Persistence    string `json:"session_affinity,omitempty"`
	PersistenceTTL *uint  `json:"session_affinity_ttl,omitempty"`

	SessionAffinityAttrs *LoadBalancerRuleOverridesSessionAffinityAttrs `json:"session_affinity_attributes,omitempty"`

	TTL uint `json:"ttl,omitempty"`

	SteeringPolicy string `json:"steering_policy,omitempty"`
	FallbackPool   string `json:"fallback_pool,omitempty"`

	DefaultPools []string            `json:"default_pools,omitempty"`
	PoPPools     map[string][]string `json:"pop_pools,omitempty"`
	RegionPools  map[string][]string `json:"region_pools,omitempty"`
	CountryPools map[string][]string `json:"country_pools,omitempty"`

	RandomSteering   *RandomSteering   `json:"random_steering,omitempty"`
	AdaptiveRouting  *AdaptiveRouting  `json:"adaptive_routing,omitempty"`
	LocationStrategy *LocationStrategy `json:"location_strategy,omitempty"`
}

// RandomSteering configures pool weights.
//
// SteeringPolicy="random": A random pool is selected with probability
// proportional to pool weights.
//
// SteeringPolicy="least_outstanding_requests": Use pool weights to
// scale each pool's outstanding requests.
//
// SteeringPolicy="least_connections": Use pool weights to
// scale each pool's open connections.
type RandomSteering struct {
	DefaultWeight float64            `json:"default_weight,omitempty"`
	PoolWeights   map[string]float64 `json:"pool_weights,omitempty"`
}

// AdaptiveRouting controls features that modify the routing of requests
// to pools and origins in response to dynamic conditions, such as during
// the interval between active health monitoring requests.
// For example, zero-downtime failover occurs immediately when an origin
// becomes unavailable due to HTTP 521, 522, or 523 response codes.
// If there is another healthy origin in the same pool, the request is
// retried once against this alternate origin.
type AdaptiveRouting struct {
	// FailoverAcrossPools extends zero-downtime failover of requests to healthy origins
	// from alternate pools, when no healthy alternate exists in the same pool, according
	// to the failover order defined by traffic and origin steering.
	// When set false (the default) zero-downtime failover will only occur between origins
	// within the same pool. See SessionAffinityAttributes for control over when sessions
	// are broken or reassigned.
	FailoverAcrossPools *bool `json:"failover_across_pools,omitempty"`
}

// LocationStrategy controls location-based steering for non-proxied requests.
// See SteeringPolicy to learn how steering is affected.
type LocationStrategy struct {
	// PreferECS determines whether the EDNS Client Subnet (ECS) GeoIP should
	// be preferred as the authoritative location.
	//
	// "always": Always prefer ECS.
	//
	// "never": Never prefer ECS.
	//
	// "proximity": (default) Prefer ECS only when SteeringPolicy="proximity".
	//
	// "geo": Prefer ECS only when SteeringPolicy="geo".
	PreferECS string `json:"prefer_ecs,omitempty"`
	// Mode determines the authoritative location when ECS is not preferred,
	// does not exist in the request, or its GeoIP lookup is unsuccessful.
	//
	// "pop": (default) Use the Cloudflare PoP location.
	//
	// "resolver_ip": Use the DNS resolver GeoIP location.
	// If the GeoIP lookup is unsuccessful, use the Cloudflare PoP location.
	Mode string `json:"mode,omitempty"`
}

// LoadBalancerRuleOverridesSessionAffinityAttrs mimics SessionAffinityAttributes without the
// DrainDuration field as that field can not be overwritten via rules.
type LoadBalancerRuleOverridesSessionAffinityAttrs struct {
	SameSite             string   `json:"samesite,omitempty"`
	Secure               string   `json:"secure,omitempty"`
	ZeroDowntimeFailover string   `json:"zero_downtime_failover,omitempty"`
	Headers              []string `json:"headers,omitempty"`
	RequireAllHeaders    *bool    `json:"require_all_headers,omitempty"`
}

// SessionAffinityAttributes represents additional configuration options for session affinity.
type SessionAffinityAttributes struct {
	SameSite             string   `json:"samesite,omitempty"`
	Secure               string   `json:"secure,omitempty"`
	DrainDuration        int      `json:"drain_duration,omitempty"`
	ZeroDowntimeFailover string   `json:"zero_downtime_failover,omitempty"`
	Headers              []string `json:"headers,omitempty"`
	RequireAllHeaders    bool     `json:"require_all_headers,omitempty"`
}

// LoadBalancerOriginHealth represents the health of the origin.
type LoadBalancerOriginHealth struct {
	Healthy       bool     `json:"healthy,omitempty"`
	RTT           Duration `json:"rtt,omitempty"`
	FailureReason string   `json:"failure_reason,omitempty"`
	ResponseCode  int      `json:"response_code,omitempty"`
}

// LoadBalancerPoolPopHealth represents the health of the pool for given PoP.
type LoadBalancerPoolPopHealth struct {
	Healthy bool                                  `json:"healthy,omitempty"`
	Origins []map[string]LoadBalancerOriginHealth `json:"origins,omitempty"`
}

// LoadBalancerPoolHealth represents the healthchecks from different PoPs for a pool.
type LoadBalancerPoolHealth struct {
	ID        string                               `json:"pool_id,omitempty"`
	PopHealth map[string]LoadBalancerPoolPopHealth `json:"pop_health,omitempty"`
}

// loadBalancerPoolResponse represents the response from the load balancer pool endpoints.
type loadBalancerPoolResponse struct {
	Response
	Result LoadBalancerPool `json:"result"`
}

// loadBalancerPoolListResponse represents the response from the List Pools endpoint.
type loadBalancerPoolListResponse struct {
	Response
	Result     []LoadBalancerPool `json:"result"`
	ResultInfo ResultInfo         `json:"result_info"`
}

// loadBalancerMonitorResponse represents the response from the load balancer monitor endpoints.
type loadBalancerMonitorResponse struct {
	Response
	Result LoadBalancerMonitor `json:"result"`
}

// loadBalancerMonitorListResponse represents the response from the List Monitors endpoint.
type loadBalancerMonitorListResponse struct {
	Response
	Result     []LoadBalancerMonitor `json:"result"`
	ResultInfo ResultInfo            `json:"result_info"`
}

// loadBalancerResponse represents the response from the load balancer endpoints.
type loadBalancerResponse struct {
	Response
	Result LoadBalancer `json:"result"`
}

// loadBalancerListResponse represents the response from the List Load Balancers endpoint.
type loadBalancerListResponse struct {
	Response
	Result     []LoadBalancer `json:"result"`
	ResultInfo ResultInfo     `json:"result_info"`
}

// loadBalancerPoolHealthResponse represents the response from the Pool Health Details endpoint.
type loadBalancerPoolHealthResponse struct {
	Response
	Result LoadBalancerPoolHealth `json:"result"`
}

type CreateLoadBalancerPoolParams struct {
	LoadBalancerPool LoadBalancerPool
}

type ListLoadBalancerPoolParams struct {
	PaginationOptions
}

type UpdateLoadBalancerPoolParams struct {
	LoadBalancer LoadBalancerPool
}

type CreateLoadBalancerMonitorParams struct {
	LoadBalancerMonitor LoadBalancerMonitor
}

type ListLoadBalancerMonitorParams struct {
	PaginationOptions
}

type UpdateLoadBalancerMonitorParams struct {
	LoadBalancerMonitor LoadBalancerMonitor
}

type CreateLoadBalancerParams struct {
	LoadBalancer LoadBalancer
}

type ListLoadBalancerParams struct {
	PaginationOptions
}

type UpdateLoadBalancerParams struct {
	LoadBalancer LoadBalancer
}

var (
	ErrMissingPoolID         = errors.New("missing required pool ID")
	ErrMissingMonitorID      = errors.New("missing required monitor ID")
	ErrMissingLoadBalancerID = errors.New("missing required load balancer ID")
)

// CreateLoadBalancerPool creates a new load balancer pool.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-create-pool
func (api *API) CreateLoadBalancerPool(ctx context.Context, rc *ResourceContainer, params CreateLoadBalancerPoolParams) (LoadBalancerPool, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerPool{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = "/user/load_balancers/pools"
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools", rc.Identifier)
	}

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.LoadBalancerPool)
	if err != nil {
		return LoadBalancerPool{}, err
	}
	var r loadBalancerPoolResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerPool{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// ListLoadBalancerPools lists load balancer pools connected to an account.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-list-pools
func (api *API) ListLoadBalancerPools(ctx context.Context, rc *ResourceContainer, params ListLoadBalancerPoolParams) ([]LoadBalancerPool, error) {
	if rc.Level == ZoneRouteLevel {
		return []LoadBalancerPool{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = "/user/load_balancers/pools"
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools", rc.Identifier)
	}

	uri = buildURI(uri, params.PaginationOptions)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	var r loadBalancerPoolListResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// GetLoadBalancerPool returns the details for a load balancer pool.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-details
func (api *API) GetLoadBalancerPool(ctx context.Context, rc *ResourceContainer, poolID string) (LoadBalancerPool, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerPool{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if poolID == "" {
		return LoadBalancerPool{}, ErrMissingPoolID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/pools/%s", poolID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools/%s", rc.Identifier, poolID)
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return LoadBalancerPool{}, err
	}
	var r loadBalancerPoolResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerPool{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// DeleteLoadBalancerPool disables and deletes a load balancer pool.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-delete-pool
func (api *API) DeleteLoadBalancerPool(ctx context.Context, rc *ResourceContainer, poolID string) error {
	if rc.Level == ZoneRouteLevel {
		return fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if poolID == "" {
		return ErrMissingPoolID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/pools/%s", poolID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools/%s", rc.Identifier, poolID)
	}

	if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil {
		return err
	}

	return nil
}

// UpdateLoadBalancerPool modifies a configured load balancer pool.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-update-pool
func (api *API) UpdateLoadBalancerPool(ctx context.Context, rc *ResourceContainer, params UpdateLoadBalancerPoolParams) (LoadBalancerPool, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerPool{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if params.LoadBalancer.ID == "" {
		return LoadBalancerPool{}, ErrMissingPoolID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/pools/%s", params.LoadBalancer.ID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools/%s", rc.Identifier, params.LoadBalancer.ID)
	}

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.LoadBalancer)
	if err != nil {
		return LoadBalancerPool{}, err
	}
	var r loadBalancerPoolResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerPool{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// CreateLoadBalancerMonitor creates a new load balancer monitor.
//
// API reference: https://api.cloudflare.com/#load-balancer-monitors-create-monitor
func (api *API) CreateLoadBalancerMonitor(ctx context.Context, rc *ResourceContainer, params CreateLoadBalancerMonitorParams) (LoadBalancerMonitor, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerMonitor{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = "/user/load_balancers/monitors"
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/monitors", rc.Identifier)
	}

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.LoadBalancerMonitor)
	if err != nil {
		return LoadBalancerMonitor{}, err
	}
	var r loadBalancerMonitorResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerMonitor{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// ListLoadBalancerMonitors lists load balancer monitors connected to an account.
//
// API reference: https://api.cloudflare.com/#load-balancer-monitors-list-monitors
func (api *API) ListLoadBalancerMonitors(ctx context.Context, rc *ResourceContainer, params ListLoadBalancerMonitorParams) ([]LoadBalancerMonitor, error) {
	if rc.Level == ZoneRouteLevel {
		return []LoadBalancerMonitor{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = "/user/load_balancers/monitors"
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/monitors", rc.Identifier)
	}

	uri = buildURI(uri, params.PaginationOptions)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	var r loadBalancerMonitorListResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// GetLoadBalancerMonitor returns the details for a load balancer monitor.
//
// API reference: https://api.cloudflare.com/#load-balancer-monitors-monitor-details
func (api *API) GetLoadBalancerMonitor(ctx context.Context, rc *ResourceContainer, monitorID string) (LoadBalancerMonitor, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerMonitor{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if monitorID == "" {
		return LoadBalancerMonitor{}, ErrMissingMonitorID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/monitors/%s", monitorID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/monitors/%s", rc.Identifier, monitorID)
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return LoadBalancerMonitor{}, err
	}
	var r loadBalancerMonitorResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerMonitor{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// DeleteLoadBalancerMonitor disables and deletes a load balancer monitor.
//
// API reference: https://api.cloudflare.com/#load-balancer-monitors-delete-monitor
func (api *API) DeleteLoadBalancerMonitor(ctx context.Context, rc *ResourceContainer, monitorID string) error {
	if rc.Level == ZoneRouteLevel {
		return fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if monitorID == "" {
		return ErrMissingMonitorID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/monitors/%s", monitorID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/monitors/%s", rc.Identifier, monitorID)
	}

	if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil {
		return err
	}
	return nil
}

// UpdateLoadBalancerMonitor modifies a configured load balancer monitor.
//
// API reference: https://api.cloudflare.com/#load-balancer-monitors-update-monitor
func (api *API) UpdateLoadBalancerMonitor(ctx context.Context, rc *ResourceContainer, params UpdateLoadBalancerMonitorParams) (LoadBalancerMonitor, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerMonitor{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if params.LoadBalancerMonitor.ID == "" {
		return LoadBalancerMonitor{}, ErrMissingMonitorID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/monitors/%s", params.LoadBalancerMonitor.ID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/monitors/%s", rc.Identifier, params.LoadBalancerMonitor.ID)
	}

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.LoadBalancerMonitor)
	if err != nil {
		return LoadBalancerMonitor{}, err
	}
	var r loadBalancerMonitorResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerMonitor{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// CreateLoadBalancer creates a new load balancer.
//
// API reference: https://api.cloudflare.com/#load-balancers-create-load-balancer
func (api *API) CreateLoadBalancer(ctx context.Context, rc *ResourceContainer, params CreateLoadBalancerParams) (LoadBalancer, error) {

	var uri string
	switch rc.Level {
	case AccountRouteLevel, ZoneRouteLevel:
		uri = fmt.Sprintf("/%s/%s/load_balancers", rc.Level, rc.Identifier)
	default:
		return LoadBalancer{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.LoadBalancer)
	if err != nil {
		return LoadBalancer{}, err
	}
	var r loadBalancerResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancer{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// ListLoadBalancers lists load balancers configured on a zone.
//
// API reference: https://api.cloudflare.com/#load-balancers-list-load-balancers
func (api *API) ListLoadBalancers(ctx context.Context, rc *ResourceContainer, params ListLoadBalancerParams) ([]LoadBalancer, error) {

	var uri string
	switch rc.Level {
	case AccountRouteLevel, ZoneRouteLevel:
		uri = fmt.Sprintf("/%s/%s/load_balancers", rc.Level, rc.Identifier)
	default:
		return nil, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	// add pagination options to the URI
	uri = buildURI(uri, params.PaginationOptions)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	var r loadBalancerListResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// GetLoadBalancer returns the details for a load balancer.
//
// API reference: https://api.cloudflare.com/#load-balancers-load-balancer-details
func (api *API) GetLoadBalancer(ctx context.Context, rc *ResourceContainer, loadbalancerID string) (LoadBalancer, error) {

	var uri string
	switch rc.Level {
	case AccountRouteLevel, ZoneRouteLevel:
		uri = fmt.Sprintf("/%s/%s/load_balancers/%s", rc.Level, rc.Identifier, loadbalancerID)
	default:
		return LoadBalancer{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	if loadbalancerID == "" {
		return LoadBalancer{}, ErrMissingLoadBalancerID
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return LoadBalancer{}, err
	}
	var r loadBalancerResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancer{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// DeleteLoadBalancer disables and deletes a load balancer.
//
// API reference: https://api.cloudflare.com/#load-balancers-delete-load-balancer
func (api *API) DeleteLoadBalancer(ctx context.Context, rc *ResourceContainer, loadbalancerID string) error {

	var uri string
	switch rc.Level {
	case AccountRouteLevel, ZoneRouteLevel:
		uri = fmt.Sprintf("/%s/%s/load_balancers/%s", rc.Level, rc.Identifier, loadbalancerID)
	default:
		return fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	if loadbalancerID == "" {
		return ErrMissingLoadBalancerID
	}

	if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil {
		return err
	}
	return nil
}

// UpdateLoadBalancer modifies a configured load balancer.
//
// API reference: https://api.cloudflare.com/#load-balancers-update-load-balancer
func (api *API) UpdateLoadBalancer(ctx context.Context, rc *ResourceContainer, params UpdateLoadBalancerParams) (LoadBalancer, error) {

	var uri string
	switch rc.Level {
	case AccountRouteLevel, ZoneRouteLevel:
		uri = fmt.Sprintf("/%s/%s/load_balancers/%s", rc.Level, rc.Identifier, params.LoadBalancer.ID)
	default:
		return LoadBalancer{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	if params.LoadBalancer.ID == "" {
		return LoadBalancer{}, ErrMissingLoadBalancerID
	}

	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.LoadBalancer)
	if err != nil {
		return LoadBalancer{}, err
	}
	var r loadBalancerResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancer{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// GetLoadBalancerPoolHealth fetches the latest healtcheck details for a single
// pool.
//
// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-health-details
func (api *API) GetLoadBalancerPoolHealth(ctx context.Context, rc *ResourceContainer, poolID string) (LoadBalancerPoolHealth, error) {
	if rc.Level == ZoneRouteLevel {
		return LoadBalancerPoolHealth{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel)
	}

	if poolID == "" {
		return LoadBalancerPoolHealth{}, ErrMissingPoolID
	}

	var uri string
	if rc.Level == UserRouteLevel {
		uri = fmt.Sprintf("/user/load_balancers/pools/%s/health", poolID)
	} else {
		uri = fmt.Sprintf("/accounts/%s/load_balancers/pools/%s/health", rc.Identifier, poolID)
	}

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return LoadBalancerPoolHealth{}, err
	}
	var r loadBalancerPoolHealthResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return LoadBalancerPoolHealth{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}
