package cloudflare

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

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

// NotificationMechanismData holds a single public facing mechanism data
// integation.
type NotificationMechanismData struct {
	Name string `json:"name"`
	ID   string `json:"id"`
}

// NotificationMechanismIntegrations is a list of all the integrations of a
// certain mechanism type e.g. all email integrations.
type NotificationMechanismIntegrations []NotificationMechanismData

// NotificationPolicy represents the notification policy created along with
// the destinations.
type NotificationPolicy struct {
	ID          string                                       `json:"id"`
	Name        string                                       `json:"name"`
	Description string                                       `json:"description"`
	Enabled     bool                                         `json:"enabled"`
	AlertType   string                                       `json:"alert_type"`
	Mechanisms  map[string]NotificationMechanismIntegrations `json:"mechanisms"`
	Created     time.Time                                    `json:"created"`
	Modified    time.Time                                    `json:"modified"`
	Conditions  map[string]interface{}                       `json:"conditions"`
	Filters     map[string][]string                          `json:"filters"`
}

// NotificationPoliciesResponse holds the response for listing all
// notification policies for an account.
type NotificationPoliciesResponse struct {
	Response
	ResultInfo
	Result []NotificationPolicy
}

// NotificationPolicyResponse holds the response type when a single policy
// is retrieved.
type NotificationPolicyResponse struct {
	Response
	Result NotificationPolicy
}

// NotificationWebhookIntegration describes the webhook information along
// with its status.
type NotificationWebhookIntegration struct {
	ID          string     `json:"id"`
	Name        string     `json:"name"`
	Type        string     `json:"type"`
	URL         string     `json:"url"`
	CreatedAt   time.Time  `json:"created_at"`
	LastSuccess *time.Time `json:"last_success"`
	LastFailure *time.Time `json:"last_failure"`
}

// NotificationWebhookResponse describes a single webhook retrieved.
type NotificationWebhookResponse struct {
	Response
	ResultInfo
	Result NotificationWebhookIntegration
}

// NotificationWebhooksResponse describes a list of webhooks retrieved.
type NotificationWebhooksResponse struct {
	Response
	ResultInfo
	Result []NotificationWebhookIntegration
}

// NotificationUpsertWebhooks describes a valid webhook request.
type NotificationUpsertWebhooks struct {
	Name   string `json:"name"`
	URL    string `json:"url"`
	Secret string `json:"secret"`
}

// NotificationPagerDutyResource describes a PagerDuty integration.
type NotificationPagerDutyResource struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// NotificationPagerDutyResponse describes the PagerDuty integration
// retrieved.
type NotificationPagerDutyResponse struct {
	Response
	ResultInfo
	Result NotificationPagerDutyResource
}

// NotificationResource describes the id of an inserted/updated/deleted
// resource.
type NotificationResource struct {
	ID string
}

// SaveResponse is returned when a resource is inserted/updated/deleted.
type SaveResponse struct {
	Response
	Result NotificationResource
}

// NotificationMechanismMetaData represents the state of the delivery
// mechanism.
type NotificationMechanismMetaData struct {
	Eligible bool   `json:"eligible"`
	Ready    bool   `json:"ready"`
	Type     string `json:"type"`
}

// NotificationMechanisms are the different possible delivery mechanisms.
type NotificationMechanisms struct {
	Email     NotificationMechanismMetaData `json:"email"`
	PagerDuty NotificationMechanismMetaData `json:"pagerduty"`
	Webhooks  NotificationMechanismMetaData `json:"webhooks,omitempty"`
}

// NotificationEligibilityResponse describes the eligible mechanisms that
// can be configured for a notification.
type NotificationEligibilityResponse struct {
	Response
	Result NotificationMechanisms
}

// NotificationsGroupedByProduct are grouped by products.
type NotificationsGroupedByProduct map[string][]NotificationAlertWithDescription

// NotificationAlertWithDescription represents the alert/notification
// available.
type NotificationAlertWithDescription struct {
	DisplayName string `json:"display_name"`
	Type        string `json:"type"`
	Description string `json:"description"`
}

// NotificationAvailableAlertsResponse describes the available
// alerts/notifications grouped by products.
type NotificationAvailableAlertsResponse struct {
	Response
	Result NotificationsGroupedByProduct
}

// NotificationHistory describes the history
// of notifications sent for an account.
type NotificationHistory struct {
	ID            string    `json:"id"`
	Name          string    `json:"name"`
	Description   string    `json:"description"`
	AlertBody     string    `json:"alert_body"`
	AlertType     string    `json:"alert_type"`
	Mechanism     string    `json:"mechanism"`
	MechanismType string    `json:"mechanism_type"`
	Sent          time.Time `json:"sent"`
}

// NotificationHistoryResponse describes the notification history
// response for an account for a specific time period.
type NotificationHistoryResponse struct {
	Response
	ResultInfo `json:"result_info"`
	Result     []NotificationHistory
}

// ListNotificationPolicies will return the notification policies
// created by a user for a specific account.
//
// API Reference: https://api.cloudflare.com/#notification-policies-properties
func (api *API) ListNotificationPolicies(ctx context.Context, accountID string) (NotificationPoliciesResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationPoliciesResponse{}, err
	}
	var r NotificationPoliciesResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// GetNotificationPolicy returns a specific created by a user, given the account
// id and the policy id.
//
// API Reference: https://api.cloudflare.com/#notification-policies-properties
func (api *API) GetNotificationPolicy(ctx context.Context, accountID, policyID string) (NotificationPolicyResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policyID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationPolicyResponse{}, err
	}
	var r NotificationPolicyResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// CreateNotificationPolicy creates a notification policy for an account.
//
// API Reference: https://api.cloudflare.com/#notification-policies-create-notification-policy
func (api *API) CreateNotificationPolicy(ctx context.Context, accountID string, policy NotificationPolicy) (SaveResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, baseURL, policy)
	if err != nil {
		return SaveResponse{}, err
	}
	return unmarshalNotificationSaveResponse(res)
}

// UpdateNotificationPolicy updates a notification policy, given the
// account id and the policy id and returns the policy id.
//
// API Reference: https://api.cloudflare.com/#notification-policies-update-notification-policy
func (api *API) UpdateNotificationPolicy(ctx context.Context, accountID string, policy *NotificationPolicy) (SaveResponse, error) {
	if policy == nil {
		return SaveResponse{}, fmt.Errorf("policy cannot be nil")
	}
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policy.ID)

	res, err := api.makeRequestContext(ctx, http.MethodPut, baseURL, policy)
	if err != nil {
		return SaveResponse{}, err
	}
	return unmarshalNotificationSaveResponse(res)
}

// DeleteNotificationPolicy deletes a notification policy for an account.
//
// API Reference: https://api.cloudflare.com/#notification-policies-delete-notification-policy
func (api *API) DeleteNotificationPolicy(ctx context.Context, accountID, policyID string) (SaveResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policyID)

	res, err := api.makeRequestContext(ctx, http.MethodDelete, baseURL, nil)
	if err != nil {
		return SaveResponse{}, err
	}
	return unmarshalNotificationSaveResponse(res)
}

// ListNotificationWebhooks will return the webhook destinations configured
// for an account.
//
// API Reference: https://api.cloudflare.com/#notification-webhooks-list-webhooks
func (api *API) ListNotificationWebhooks(ctx context.Context, accountID string) (NotificationWebhooksResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationWebhooksResponse{}, err
	}
	var r NotificationWebhooksResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// CreateNotificationWebhooks will help connect a webhooks destination.
// A test message will be sent to the webhooks endpoint during creation.
// If added successfully, the webhooks can be setup as a destination mechanism
// while creating policies.
//
// Notifications will be posted to this URL.
//
// API Reference: https://api.cloudflare.com/#notification-webhooks-create-webhook
func (api *API) CreateNotificationWebhooks(ctx context.Context, accountID string, webhooks *NotificationUpsertWebhooks) (SaveResponse, error) {
	if webhooks == nil {
		return SaveResponse{}, fmt.Errorf("webhooks cannot be nil")
	}
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, baseURL, webhooks)
	if err != nil {
		return SaveResponse{}, err
	}

	return unmarshalNotificationSaveResponse(res)
}

// GetNotificationWebhooks will return a specific webhook destination,
// given the account and webhooks ids.
//
// API Reference: https://api.cloudflare.com/#notification-webhooks-get-webhook
func (api *API) GetNotificationWebhooks(ctx context.Context, accountID, webhookID string) (NotificationWebhookResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationWebhookResponse{}, err
	}
	var r NotificationWebhookResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// UpdateNotificationWebhooks will update a particular webhook's name,
// given the account and webhooks ids.
//
// The webhook url and secret cannot be updated.
//
// API Reference: https://api.cloudflare.com/#notification-webhooks-update-webhook
func (api *API) UpdateNotificationWebhooks(ctx context.Context, accountID, webhookID string, webhooks *NotificationUpsertWebhooks) (SaveResponse, error) {
	if webhooks == nil {
		return SaveResponse{}, fmt.Errorf("webhooks cannot be nil")
	}
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID)

	res, err := api.makeRequestContext(ctx, http.MethodPut, baseURL, webhooks)
	if err != nil {
		return SaveResponse{}, err
	}

	return unmarshalNotificationSaveResponse(res)
}

// DeleteNotificationWebhooks will delete a webhook, given the account and
// webhooks ids. Deleting the webhooks will remove it from any connected
// notification policies.
//
// API Reference: https://api.cloudflare.com/#notification-webhooks-delete-webhook
func (api *API) DeleteNotificationWebhooks(ctx context.Context, accountID, webhookID string) (SaveResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID)

	res, err := api.makeRequestContext(ctx, http.MethodDelete, baseURL, nil)
	if err != nil {
		return SaveResponse{}, err
	}

	return unmarshalNotificationSaveResponse(res)
}

// ListPagerDutyNotificationDestinations will return the pagerduty
// destinations configured for an account.
//
// API Reference: https://api.cloudflare.com/#notification-destinations-with-pagerduty-list-pagerduty-services
func (api *API) ListPagerDutyNotificationDestinations(ctx context.Context, accountID string) (NotificationPagerDutyResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/pagerduty", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationPagerDutyResponse{}, err
	}
	var r NotificationPagerDutyResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// GetEligibleNotificationDestinations will return the types of
// destinations an account is eligible to configure.
//
// API Reference: https://api.cloudflare.com/#notification-mechanism-eligibility-properties
func (api *API) GetEligibleNotificationDestinations(ctx context.Context, accountID string) (NotificationEligibilityResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/eligible", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationEligibilityResponse{}, err
	}
	var r NotificationEligibilityResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// GetAvailableNotificationTypes will return the alert types available for
// a given account.
//
// API Reference: https://api.cloudflare.com/#notification-mechanism-eligibility-properties
func (api *API) GetAvailableNotificationTypes(ctx context.Context, accountID string) (NotificationAvailableAlertsResponse, error) {
	baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/available_alerts", accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil {
		return NotificationAvailableAlertsResponse{}, err
	}
	var r NotificationAvailableAlertsResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}

// TimeRange is an object for filtering the alert history based on timestamp.
type TimeRange struct {
	Since  string `json:"since,omitempty" url:"since,omitempty"`
	Before string `json:"before,omitempty" url:"before,omitempty"`
}

// AlertHistoryFilter is an object for filtering the alert history response from the api.
type AlertHistoryFilter struct {
	TimeRange
	PaginationOptions
}

// ListNotificationHistory will return the history of alerts sent for
// a given account. The time period varies based on zone plan.
// Free, Biz, Pro = 30 days
// Ent = 90 days
//
// API Reference: https://api.cloudflare.com/#notification-history-list-history
func (api *API) ListNotificationHistory(ctx context.Context, accountID string, alertHistoryFilter AlertHistoryFilter) ([]NotificationHistory, ResultInfo, error) {
	uri := buildURI(fmt.Sprintf("/accounts/%s/alerting/v3/history", accountID), alertHistoryFilter)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []NotificationHistory{}, ResultInfo{}, err
	}
	var r NotificationHistoryResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return []NotificationHistory{}, ResultInfo{}, err
	}
	return r.Result, r.ResultInfo, nil
}

// unmarshal will unmarshal bytes and return a SaveResponse.
func unmarshalNotificationSaveResponse(res []byte) (SaveResponse, error) {
	var r SaveResponse
	err := json.Unmarshal(res, &r)
	if err != nil {
		return r, err
	}
	return r, nil
}
