package salesforce

import (
	"context"
	"encoding/json"
	"errors"
	"time"

	"github.com/shopspring/decimal"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/util/retry"
)

const (
	syncLeadsApiPrefix          = "/services/apexrest/SyncLeads"
	updateAccountApiPrefix      = "/services/apexrest/UpdateAccount"
	syncPocApplicationApiPrefix = "/services/apexrest/addpocandresults"
	syncPOCCreditsApiPrefix     = "/services/apexrest/updateAccountRemaining"
)

var (
	defaultRetry = wait.Backoff{
		Steps:    3,
		Duration: 10 * time.Millisecond,
		Factor:   1.0,
		Jitter:   0.1,
	}
)

type Lead struct {
	Email         string `json:"email"`
	Name          string `json:"name"`
	Country       string `json:"country"`
	Plan          string `json:"plan"`
	TimeZone      string `json:"time_zone"`
	PortalID      string `json:"portal_id"`
	FirstName     string `json:"first_name"`
	LastName      string `json:"last_name"`
	CreatedAt     string `json:"created_at"`
	AcceptService int    `json:"accept_service"`
	AgreementTime string `json:"agreement_time"`
	IPAddress     string `json:"ip_address"`
	PhoneNumber   string `json:"phone_number"`
	Job           string `json:"job"`
	Source        string `json:"source"`
	// The following fields are custom fields of Salesforce by Biz Ops.
	// The field name should be small case.
	// These first-level fields are automatically generated for the Salesforce SyncLeads API.
	// Refer to the Salesforce Dev, they can't have these fields as nested JSON fields.
	ConnectionType        string `json:"Connection_Type"`
	WebsiteReferrerURL    string `json:"Website_Referrer_URL"`
	UtmCampaign           string `json:"utm_campaign"`
	UtmContent            string `json:"utm_content"`
	UtmMedium             string `json:"utm_medium"`
	UtmSource             string `json:"utm_source"`
	UtmTerm               string `json:"utm_term"`
	MostRecentReferralUrl string `json:"most_recent_referrer_url"`
}

func (c *Client) syncLeads(ctx context.Context, lead Lead) error {
	resp, err := c.post(ctx, syncLeadsApiPrefix, lead)
	if err != nil {
		return err
	}

	var result Response
	err = json.Unmarshal(resp, &result)
	if err != nil {
		return err
	}
	if result.Code == "0" {
		return nil
	}
	return errors.New(string(resp))
}

// SyncLeads API
func (c *Client) SyncLeads(ctx context.Context, lead Lead) error {
	return retry.OnError(defaultRetry, func(e error) bool { return e != nil }, func() error {
		return c.syncLeads(ctx, lead)
	})
}

type Account struct {
	Plan             string `json:"plan"`
	PortalID         string `json:"portal_id"`
	BillingEmail     string `json:"billing_email"`
	BindCreditCardAt string `json:"bind_credit_card_at"`
	Source           string `json:"source"`
}

func (c *Client) updateAccount(ctx context.Context, account Account) error {
	resp, err := c.post(ctx, updateAccountApiPrefix, account)
	if err != nil {
		return err
	}

	var result Response
	err = json.Unmarshal(resp, &result)
	if err != nil {
		return err
	}
	if result.Code == "0" {
		return nil
	}
	return errors.New(string(resp))
}

// UpdateAccount API
func (c *Client) UpdateAccount(ctx context.Context, account Account) error {
	return retry.OnError(defaultRetry, func(e error) bool { return e != nil }, func() error {
		return c.updateAccount(ctx, account)
	})
}

type PocApplication struct {
	PortalID      string        `json:"portalId"`
	PocID         string        `json:"pocId"`
	PocResults    []PocQuestion `json:"pocResults"`
	PocRecordType string        `json:"pocRecordType"`
	SubTopic      string        `json:"subtopic"`
	Message       string        `json:"message"`
}

type PocQuestionType string

const (
	PocQuestionTypeRadio    PocQuestionType = "Radio"
	PocQuestionTypeCheckbox PocQuestionType = "Checkbox"
	PocQuestionTypeText     PocQuestionType = "Text"
)

type PocQuestion struct {
	QuestionNumber int                 `json:"questionNumber"`
	Type           PocQuestionType     `json:"type"`
	Question       string              `json:"question"`
	Options        []PocQuestionOption `json:"options"`
	Note           string              `json:"note"`
}

type PocQuestionOption struct {
	OptionName string `json:"optionName"`
	IsSelected bool   `json:"isSelected"`
}

func (c *Client) syncPocApplication(ctx context.Context, pocApplication PocApplication) error {
	resp, err := c.post(ctx, syncPocApplicationApiPrefix, pocApplication)
	if err != nil {
		return err
	}

	var result Response
	err = json.Unmarshal(resp, &result)
	if err != nil {
		return err
	}
	if result.Code == "0" {
		return nil
	}
	return errors.New(string(resp))
}

func (c *Client) SyncPocApplication(ctx context.Context, pocApplication PocApplication) error {
	return retry.OnError(defaultRetry, func(e error) bool { return e != nil }, func() error {
		return c.syncPocApplication(ctx, pocApplication)
	})
}

type POCCredits struct {
	PortalID         string          `json:"portal_id"`
	RemainingCredits decimal.Decimal `json:"remaining_credits"`
}

func (c *Client) syncPOCCredits(ctx context.Context, pocCredits POCCredits) error {
	resp, err := c.post(ctx, syncPOCCreditsApiPrefix, pocCredits)
	if err != nil {
		return err
	}

	var result Response
	err = json.Unmarshal(resp, &result)
	if err != nil {
		return err
	}
	if result.Code == "0" {
		return nil
	}
	return errors.New(string(resp))
}

func (c *Client) SyncPOCCredits(ctx context.Context, pocCredits POCCredits) error {
	return retry.OnError(defaultRetry, func(e error) bool { return e != nil }, func() error {
		return c.syncPOCCredits(ctx, pocCredits)
	})
}
