package cloudflare

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

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

type Enabled struct {
	Enabled bool `json:"enabled"`
}

// DeviceClientCertificates identifies if the zero trust zone is configured for an account.
type DeviceClientCertificates struct {
	Response
	Result Enabled
}

type ServiceMode string

const (
	oneDotOne      ServiceMode = "1dot1"
	warp           ServiceMode = "warp"
	proxy          ServiceMode = "proxy"
	postureOnly    ServiceMode = "posture_only"
	warpTunnelOnly ServiceMode = "warp_tunnel_only"

	listDeviceSettingsPoliciesDefaultPageSize = 20
)

type ServiceModeV2 struct {
	Mode ServiceMode `json:"mode,omitempty"`
	Port int         `json:"port,omitempty"`
}

type DeviceSettingsPolicy struct {
	ServiceModeV2       *ServiceModeV2    `json:"service_mode_v2"`
	DisableAutoFallback *bool             `json:"disable_auto_fallback"`
	FallbackDomains     *[]FallbackDomain `json:"fallback_domains"`
	Include             *[]SplitTunnel    `json:"include"`
	Exclude             *[]SplitTunnel    `json:"exclude"`
	GatewayUniqueID     *string           `json:"gateway_unique_id"`
	SupportURL          *string           `json:"support_url"`
	CaptivePortal       *int              `json:"captive_portal"`
	AllowModeSwitch     *bool             `json:"allow_mode_switch"`
	SwitchLocked        *bool             `json:"switch_locked"`
	AllowUpdates        *bool             `json:"allow_updates"`
	AutoConnect         *int              `json:"auto_connect"`
	AllowedToLeave      *bool             `json:"allowed_to_leave"`
	PolicyID            *string           `json:"policy_id"`
	Enabled             *bool             `json:"enabled"`
	Name                *string           `json:"name"`
	Match               *string           `json:"match"`
	Precedence          *int              `json:"precedence"`
	Default             bool              `json:"default"`
	ExcludeOfficeIps    *bool             `json:"exclude_office_ips"`
	Description         *string           `json:"description"`
	LANAllowMinutes     *uint             `json:"lan_allow_minutes"`
	LANAllowSubnetSize  *uint             `json:"lan_allow_subnet_size"`
	TunnelProtocol      *string           `json:"tunnel_protocol"`
}

type DeviceSettingsPolicyResponse struct {
	Response
	Result DeviceSettingsPolicy
}

type DeleteDeviceSettingsPolicyResponse struct {
	Response
	Result []DeviceSettingsPolicy
}

type CreateDeviceSettingsPolicyParams struct {
	DisableAutoFallback *bool          `json:"disable_auto_fallback,omitempty"`
	CaptivePortal       *int           `json:"captive_portal,omitempty"`
	AllowModeSwitch     *bool          `json:"allow_mode_switch,omitempty"`
	SwitchLocked        *bool          `json:"switch_locked,omitempty"`
	AllowUpdates        *bool          `json:"allow_updates,omitempty"`
	AutoConnect         *int           `json:"auto_connect,omitempty"`
	AllowedToLeave      *bool          `json:"allowed_to_leave,omitempty"`
	SupportURL          *string        `json:"support_url,omitempty"`
	ServiceModeV2       *ServiceModeV2 `json:"service_mode_v2,omitempty"`
	Precedence          *int           `json:"precedence,omitempty"`
	Name                *string        `json:"name,omitempty"`
	Match               *string        `json:"match,omitempty"`
	Enabled             *bool          `json:"enabled,omitempty"`
	ExcludeOfficeIps    *bool          `json:"exclude_office_ips"`
	Description         *string        `json:"description,omitempty"`
	LANAllowMinutes     *uint          `json:"lan_allow_minutes,omitempty"`
	LANAllowSubnetSize  *uint          `json:"lan_allow_subnet_size,omitempty"`
	TunnelProtocol      *string        `json:"tunnel_protocol,omitempty"`
}

type UpdateDefaultDeviceSettingsPolicyParams struct {
	DisableAutoFallback *bool          `json:"disable_auto_fallback,omitempty"`
	CaptivePortal       *int           `json:"captive_portal,omitempty"`
	AllowModeSwitch     *bool          `json:"allow_mode_switch,omitempty"`
	SwitchLocked        *bool          `json:"switch_locked,omitempty"`
	AllowUpdates        *bool          `json:"allow_updates,omitempty"`
	AutoConnect         *int           `json:"auto_connect,omitempty"`
	AllowedToLeave      *bool          `json:"allowed_to_leave,omitempty"`
	SupportURL          *string        `json:"support_url,omitempty"`
	ServiceModeV2       *ServiceModeV2 `json:"service_mode_v2,omitempty"`
	Precedence          *int           `json:"precedence,omitempty"`
	Name                *string        `json:"name,omitempty"`
	Match               *string        `json:"match,omitempty"`
	Enabled             *bool          `json:"enabled,omitempty"`
	ExcludeOfficeIps    *bool          `json:"exclude_office_ips"`
	Description         *string        `json:"description,omitempty"`
	LANAllowMinutes     *uint          `json:"lan_allow_minutes,omitempty"`
	LANAllowSubnetSize  *uint          `json:"lan_allow_subnet_size,omitempty"`
	TunnelProtocol      *string        `json:"tunnel_protocol,omitempty"`
}

type UpdateDeviceSettingsPolicyParams struct {
	PolicyID            *string        `json:"-"`
	DisableAutoFallback *bool          `json:"disable_auto_fallback,omitempty"`
	CaptivePortal       *int           `json:"captive_portal,omitempty"`
	AllowModeSwitch     *bool          `json:"allow_mode_switch,omitempty"`
	SwitchLocked        *bool          `json:"switch_locked,omitempty"`
	AllowUpdates        *bool          `json:"allow_updates,omitempty"`
	AutoConnect         *int           `json:"auto_connect,omitempty"`
	AllowedToLeave      *bool          `json:"allowed_to_leave,omitempty"`
	SupportURL          *string        `json:"support_url,omitempty"`
	ServiceModeV2       *ServiceModeV2 `json:"service_mode_v2,omitempty"`
	Precedence          *int           `json:"precedence,omitempty"`
	Name                *string        `json:"name,omitempty"`
	Match               *string        `json:"match,omitempty"`
	Enabled             *bool          `json:"enabled,omitempty"`
	ExcludeOfficeIps    *bool          `json:"exclude_office_ips"`
	Description         *string        `json:"description,omitempty"`
	LANAllowMinutes     *uint          `json:"lan_allow_minutes,omitempty"`
	LANAllowSubnetSize  *uint          `json:"lan_allow_subnet_size,omitempty"`
	TunnelProtocol      *string        `json:"tunnel_protocol,omitempty"`
}

type ListDeviceSettingsPoliciesResponse struct {
	Response
	ResultInfo ResultInfo             `json:"result_info"`
	Result     []DeviceSettingsPolicy `json:"result"`
}

type UpdateDeviceClientCertificatesParams struct {
	Enabled *bool `json:"enabled"`
}

type GetDeviceClientCertificatesParams struct{}

type GetDefaultDeviceSettingsPolicyParams struct{}

type GetDeviceSettingsPolicyParams struct {
	PolicyID *string `json:"-"`
}

// UpdateDeviceClientCertificates controls the zero trust zone used to provision client certificates.
//
// API reference: https://api.cloudflare.com/#device-client-certificates
func (api *API) UpdateDeviceClientCertificates(ctx context.Context, rc *ResourceContainer, params UpdateDeviceClientCertificatesParams) (DeviceClientCertificates, error) {
	if rc.Level != ZoneRouteLevel {
		return DeviceClientCertificates{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy/certificates", rc.Level, rc.Identifier)

	result := DeviceClientCertificates{Result: Enabled{false}}
	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return result, err
	}

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

	return result, err
}

// GetDeviceClientCertificates controls the zero trust zone used to provision
// client certificates.
//
// API reference: https://api.cloudflare.com/#device-client-certificates
func (api *API) GetDeviceClientCertificates(ctx context.Context, rc *ResourceContainer, params GetDeviceClientCertificatesParams) (DeviceClientCertificates, error) {
	if rc.Level != ZoneRouteLevel {
		return DeviceClientCertificates{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy/certificates", rc.Level, rc.Identifier)

	result := DeviceClientCertificates{}
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return result, err
	}

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

	return result, err
}

// CreateDeviceSettingsPolicy creates a settings policy against devices that
// match the policy.
//
// API reference: https://api.cloudflare.com/#devices-create-device-settings-policy
func (api *API) CreateDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, params CreateDeviceSettingsPolicyParams) (DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy", rc.Level, rc.Identifier)

	result := DeviceSettingsPolicyResponse{}
	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
	if err != nil {
		return DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

// UpdateDefaultDeviceSettingsPolicy updates the default settings policy for an account
//
// API reference: https://api.cloudflare.com/#devices-update-default-device-settings-policy
func (api *API) UpdateDefaultDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, params UpdateDefaultDeviceSettingsPolicyParams) (DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	result := DeviceSettingsPolicyResponse{}
	uri := fmt.Sprintf("/%s/%s/devices/policy", rc.Level, rc.Identifier)
	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

// UpdateDeviceSettingsPolicy updates a settings policy
//
// API reference: https://api.cloudflare.com/#devices-update-device-settings-policy
func (api *API) UpdateDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, params UpdateDeviceSettingsPolicyParams) (DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy/%s", rc.Level, rc.Identifier, *params.PolicyID)

	result := DeviceSettingsPolicyResponse{}
	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
	if err != nil {
		return DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

// DeleteDeviceSettingsPolicy deletes a settings policy and returns a list
// of all of the other policies in the account.
//
// API reference: https://api.cloudflare.com/#devices-delete-device-settings-policy
func (api *API) DeleteDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, policyID string) ([]DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return []DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy/%s", rc.Level, rc.Identifier, policyID)

	result := DeleteDeviceSettingsPolicyResponse{}
	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return []DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

// GetDefaultDeviceSettings gets the default device settings policy.
//
// API reference: https://api.cloudflare.com/#devices-get-default-device-settings-policy
func (api *API) GetDefaultDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, params GetDefaultDeviceSettingsPolicyParams) (DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy", rc.Level, rc.Identifier)

	result := DeviceSettingsPolicyResponse{}
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

// GetDefaultDeviceSettings gets the device settings policy by its policyID.
//
// API reference: https://api.cloudflare.com/#devices-get-device-settings-policy-by-id
func (api *API) GetDeviceSettingsPolicy(ctx context.Context, rc *ResourceContainer, params GetDeviceSettingsPolicyParams) (DeviceSettingsPolicy, error) {
	if rc.Level != AccountRouteLevel {
		return DeviceSettingsPolicy{}, fmt.Errorf(errInvalidResourceContainerAccess, rc.Level)
	}

	uri := fmt.Sprintf("/%s/%s/devices/policy/%s", rc.Level, rc.Identifier, *params.PolicyID)

	result := DeviceSettingsPolicyResponse{}
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return DeviceSettingsPolicy{}, err
	}

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

	return result.Result, err
}

type ListDeviceSettingsPoliciesParams struct {
	ResultInfo
}

// ListDeviceSettingsPolicies returns all device settings policies for an account
//
// API reference: https://api.cloudflare.com/#devices-list-device-settings-policies
func (api *API) ListDeviceSettingsPolicies(ctx context.Context, rc *ResourceContainer, params ListDeviceSettingsPoliciesParams) ([]DeviceSettingsPolicy, *ResultInfo, error) {
	autoPaginate := true
	if params.PerPage >= 1 || params.Page >= 1 {
		autoPaginate = false
	}

	if params.PerPage < 1 {
		params.PerPage = listDeviceSettingsPoliciesDefaultPageSize
	}

	var policies []DeviceSettingsPolicy
	var lastResultInfo ResultInfo
	for {
		uri := buildURI(fmt.Sprintf("/%s/%s/devices/policies", rc.Level, rc.Identifier), params)
		res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
		if err != nil {
			return nil, nil, err
		}
		var r ListDeviceSettingsPoliciesResponse
		err = json.Unmarshal(res, &r)
		if err != nil {
			return nil, nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
		}
		policies = append(policies, r.Result...)
		lastResultInfo = r.ResultInfo
		params.ResultInfo = r.ResultInfo.Next()
		if params.ResultInfo.Done() || !autoPaginate {
			break
		}
	}
	return policies, &lastResultInfo, nil
}
