package cloudflare

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

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

// AddressMap contains information about an address map.
type AddressMap struct {
	ID           string                 `json:"id"`
	Description  *string                `json:"description,omitempty"`
	DefaultSNI   *string                `json:"default_sni"`
	Enabled      *bool                  `json:"enabled"`
	Deletable    *bool                  `json:"can_delete"`
	CanModifyIPs *bool                  `json:"can_modify_ips"`
	Memberships  []AddressMapMembership `json:"memberships"`
	IPs          []AddressMapIP         `json:"ips"`
	CreatedAt    time.Time              `json:"created_at"`
	ModifiedAt   time.Time              `json:"modified_at"`
}

type AddressMapIP struct {
	IP        string    `json:"ip"`
	CreatedAt time.Time `json:"created_at"`
}

type AddressMapMembershipContainer struct {
	Identifier string                   `json:"identifier"`
	Kind       AddressMapMembershipKind `json:"kind"`
}

type AddressMapMembership struct {
	Identifier string                   `json:"identifier"`
	Kind       AddressMapMembershipKind `json:"kind"`
	Deletable  *bool                    `json:"can_delete"`
	CreatedAt  time.Time                `json:"created_at"`
}

func (ammb *AddressMapMembershipContainer) URLFragment() string {
	switch ammb.Kind {
	case AddressMapMembershipAccount:
		return fmt.Sprintf("accounts/%s", ammb.Identifier)
	case AddressMapMembershipZone:
		return fmt.Sprintf("zones/%s", ammb.Identifier)
	default:
		return fmt.Sprintf("%s/%s", ammb.Kind, ammb.Identifier)
	}
}

type AddressMapMembershipKind string

const (
	AddressMapMembershipZone    AddressMapMembershipKind = "zone"
	AddressMapMembershipAccount AddressMapMembershipKind = "account"
)

// ListAddressMapResponse contains a slice of address maps.
type ListAddressMapResponse struct {
	Response
	Result []AddressMap `json:"result"`
}

// GetAddressMapResponse contains a specific address map's API Response.
type GetAddressMapResponse struct {
	Response
	Result AddressMap `json:"result"`
}

// CreateAddressMapParams contains information about an address map to be created.
type CreateAddressMapParams struct {
	Description *string                         `json:"description"`
	Enabled     *bool                           `json:"enabled"`
	IPs         []string                        `json:"ips"`
	Memberships []AddressMapMembershipContainer `json:"memberships"`
}

// UpdateAddressMapParams contains information about an address map to be updated.
type UpdateAddressMapParams struct {
	ID          string  `json:"-"`
	Description *string `json:"description,omitempty"`
	Enabled     *bool   `json:"enabled,omitempty"`
	DefaultSNI  *string `json:"default_sni,omitempty"`
}

// AddressMapFilter contains filter parameters for finding a list of address maps.
type ListAddressMapsParams struct {
	IP   *string `url:"ip,omitempty"`
	CIDR *string `url:"cidr,omitempty"`
}

// CreateIPAddressToAddressMapParams contains request parameters to add/remove IP address to/from an address map.
type CreateIPAddressToAddressMapParams struct {
	// ID represents the target address map for adding the IP address.
	ID string
	// The IP address.
	IP string
}

// CreateMembershipToAddressMapParams contains request parameters to add/remove membership from an address map.
type CreateMembershipToAddressMapParams struct {
	// ID represents the target address map for adding the membershp.
	ID         string
	Membership AddressMapMembershipContainer
}

type DeleteMembershipFromAddressMapParams struct {
	// ID represents the target address map for removing the IP address.
	ID         string
	Membership AddressMapMembershipContainer
}

type DeleteIPAddressFromAddressMapParams struct {
	// ID represents the target address map for adding the membershp.
	ID string
	IP string
}

// ListAddressMaps lists all address maps owned by the account.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/methods/list/
func (api *API) ListAddressMaps(ctx context.Context, rc *ResourceContainer, params ListAddressMapsParams) ([]AddressMap, error) {
	if rc.Level != AccountRouteLevel {
		return []AddressMap{}, ErrRequiredAccountLevelResourceContainer
	}

	uri := buildURI(fmt.Sprintf("/%s/addressing/address_maps", rc.URLFragment()), params)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []AddressMap{}, err
	}

	result := ListAddressMapResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return []AddressMap{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result.Result, nil
}

// CreateAddressMap creates a new address map under the account.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/methods/create/
func (api *API) CreateAddressMap(ctx context.Context, rc *ResourceContainer, params CreateAddressMapParams) (AddressMap, error) {
	if rc.Level != AccountRouteLevel {
		return AddressMap{}, ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps", rc.URLFragment())
	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
	if err != nil {
		return AddressMap{}, err
	}

	result := GetAddressMapResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return AddressMap{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result.Result, nil
}

// GetAddressMap returns a specific address map.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/methods/get/
func (api *API) GetAddressMap(ctx context.Context, rc *ResourceContainer, id string) (AddressMap, error) {
	if rc.Level != AccountRouteLevel {
		return AddressMap{}, ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s", rc.URLFragment(), id)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return AddressMap{}, err
	}

	result := GetAddressMapResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return AddressMap{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result.Result, nil
}

// UpdateAddressMap modifies properties of an address map owned by the account.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/methods/edit/
func (api *API) UpdateAddressMap(ctx context.Context, rc *ResourceContainer, params UpdateAddressMapParams) (AddressMap, error) {
	if rc.Level != AccountRouteLevel {
		return AddressMap{}, ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s", rc.URLFragment(), params.ID)
	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return AddressMap{}, err
	}

	result := GetAddressMapResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return AddressMap{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result.Result, nil
}

// DeleteAddressMap deletes a particular address map owned by the account.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/methods/delete/
func (api *API) DeleteAddressMap(ctx context.Context, rc *ResourceContainer, id string) error {
	if rc.Level != AccountRouteLevel {
		return ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s", rc.URLFragment(), id)
	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	return err
}

// CreateIPAddressToAddressMap adds an IP address from a prefix owned by the account to a particular address map.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/ips/methods/update/
func (api *API) CreateIPAddressToAddressMap(ctx context.Context, rc *ResourceContainer, params CreateIPAddressToAddressMapParams) error {
	if rc.Level != AccountRouteLevel {
		return ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s/ips/%s", rc.URLFragment(), params.ID, params.IP)
	_, err := api.makeRequestContext(ctx, http.MethodPut, uri, nil)
	return err
}

// DeleteIPAddressFromAddressMap removes an IP address from a particular address map.
//
// API reference: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/ips/methods/delete/
func (api *API) DeleteIPAddressFromAddressMap(ctx context.Context, rc *ResourceContainer, params DeleteIPAddressFromAddressMapParams) error {
	if rc.Level != AccountRouteLevel {
		return ErrRequiredAccountLevelResourceContainer
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s/ips/%s", rc.URLFragment(), params.ID, params.IP)
	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	return err
}

// CreateMembershipToAddressMap adds a zone/account as a member of a particular address map.
//
// API reference:
//   - account: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/accounts/methods/update/
//   - zone: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/zones/methods/update/
func (api *API) CreateMembershipToAddressMap(ctx context.Context, rc *ResourceContainer, params CreateMembershipToAddressMapParams) error {
	if rc.Level != AccountRouteLevel {
		return ErrRequiredAccountLevelResourceContainer
	}

	if params.Membership.Kind != AddressMapMembershipZone && params.Membership.Kind != AddressMapMembershipAccount {
		return fmt.Errorf("requested membershp kind (%q) is not supported", params.Membership.Kind)
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s/%s", rc.URLFragment(), params.ID, params.Membership.URLFragment())
	_, err := api.makeRequestContext(ctx, http.MethodPut, uri, nil)
	return err
}

// DeleteMembershipFromAddressMap removes a zone/account as a member of a particular address map.
//
// API reference:
//   - account: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/accounts/methods/delete/
//   - zone: https://developers.cloudflare.com/api/resources/addressing/subresources/address_maps/subresources/zones/methods/delete/
func (api *API) DeleteMembershipFromAddressMap(ctx context.Context, rc *ResourceContainer, params DeleteMembershipFromAddressMapParams) error {
	if rc.Level != AccountRouteLevel {
		return ErrRequiredAccountLevelResourceContainer
	}

	if params.Membership.Kind != AddressMapMembershipZone && params.Membership.Kind != AddressMapMembershipAccount {
		return fmt.Errorf("requested membershp kind (%q) is not supported", params.Membership.Kind)
	}

	uri := fmt.Sprintf("/%s/addressing/address_maps/%s/%s", rc.URLFragment(), params.ID, params.Membership.URLFragment())
	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	return err
}
