// ampli.go
//
// Ampli - A strong typed wrapper for your Analytics
//
// This file is generated by Amplitude.
// To update run 'ampli pull backend'
//
// Required dependencies: github.com/amplitude/analytics-go@latest
// Tracking Plan Version: 2
// Build: 1.0.0
// Runtime: go-ampli
//
// View Tracking Plan: https://data.eu.amplitude.com/profee/Profee%20Tips/events/main/latest
//
// Full Setup Instructions: https://data.eu.amplitude.com/profee/Profee%20Tips/implementation/main/latest/getting-started/backend
//

package ampli

import (
	"log"
	"sync"

	"github.com/amplitude/analytics-go/amplitude"
)

type (
	EventOptions  = amplitude.EventOptions
	ExecuteResult = amplitude.ExecuteResult
)

const (
	IdentifyEventType      = amplitude.IdentifyEventType
	GroupIdentifyEventType = amplitude.GroupIdentifyEventType

	ServerZoneUS = amplitude.ServerZoneUS
	ServerZoneEU = amplitude.ServerZoneEU
)

var (
	NewClientConfig = amplitude.NewConfig
	NewClient       = amplitude.NewClient
)

var Instance = Ampli{}

type Environment string

const (
	EnvironmentProfeetips Environment = `profeetips`
)

var APIKey = map[Environment]string{
	EnvironmentProfeetips: `c4e543cf70e8c83b85eb56e9a1d9b4b3`,
}

// LoadClientOptions is Client options setting to initialize Ampli client.
//
// Params:
//   - APIKey: the API key of Amplitude project
//   - Instance: the core SDK instance used by Ampli client
//   - Configuration: the core SDK client configuration instance
type LoadClientOptions struct {
	APIKey        string
	Instance      amplitude.Client
	Configuration amplitude.Config
}

// LoadOptions is options setting to initialize Ampli client.
//
// Params:
//   - Environment: the environment of Amplitude Data project
//   - Disabled: the flag of disabled Ampli client
//   - Client: the LoadClientOptions struct
type LoadOptions struct {
	Environment Environment
	Disabled    bool
	Client      LoadClientOptions
}

type baseEvent struct {
	eventType  string
	properties map[string]any
}

type Event interface {
	ToAmplitudeEvent() amplitude.Event
}

func newBaseEvent(eventType string, properties map[string]any) baseEvent {
	return baseEvent{
		eventType:  eventType,
		properties: properties,
	}
}

func (event baseEvent) ToAmplitudeEvent() amplitude.Event {
	return amplitude.Event{
		EventType:       event.eventType,
		EventProperties: event.properties,
	}
}

var EmailOpened = struct {
	Builder func() interface {
		EmailType(emailType string) EmailOpenedBuilder
	}
}{
	Builder: func() interface {
		EmailType(emailType string) EmailOpenedBuilder
	} {
		return &emailOpenedBuilder{
			properties: map[string]any{},
		}
	},
}

type EmailOpenedEvent interface {
	Event
	emailOpened()
}

type emailOpenedEvent struct {
	baseEvent
}

func (e emailOpenedEvent) emailOpened() {
}

type EmailOpenedBuilder interface {
	Build() EmailOpenedEvent
}

type emailOpenedBuilder struct {
	properties map[string]any
}

func (b *emailOpenedBuilder) EmailType(emailType string) EmailOpenedBuilder {
	b.properties[`emailType`] = emailType

	return b
}

func (b *emailOpenedBuilder) Build() EmailOpenedEvent {
	return &emailOpenedEvent{
		newBaseEvent(`emailOpened`, b.properties),
	}
}

var EmailSent = struct {
	Builder func() interface {
		Domain(domain string) interface {
			EmailType(emailType string) EmailSentBuilder
		}
	}
}{
	Builder: func() interface {
		Domain(domain string) interface {
			EmailType(emailType string) EmailSentBuilder
		}
	} {
		return &emailSentBuilder{
			properties: map[string]any{},
		}
	},
}

type EmailSentEvent interface {
	Event
	emailSent()
}

type emailSentEvent struct {
	baseEvent
}

func (e emailSentEvent) emailSent() {
}

type EmailSentBuilder interface {
	Build() EmailSentEvent
}

type emailSentBuilder struct {
	properties map[string]any
}

func (b *emailSentBuilder) Domain(domain string) interface {
	EmailType(emailType string) EmailSentBuilder
} {
	b.properties[`domain`] = domain

	return b
}

func (b *emailSentBuilder) EmailType(emailType string) EmailSentBuilder {
	b.properties[`emailType`] = emailType

	return b
}

func (b *emailSentBuilder) Build() EmailSentEvent {
	return &emailSentEvent{
		newBaseEvent(`emailSent`, b.properties),
	}
}

var PaymentFailed = struct {
	Builder func() interface {
		Amount(amount float64) interface {
			Domain(domain string) interface {
				Fee(fee float64) interface {
					FeeCoveredBy(feeCoveredBy string) interface {
						Product(product string) interface {
							ProductQty(productQty int) PaymentFailedBuilder
						}
					}
				}
			}
		}
	}
}{
	Builder: func() interface {
		Amount(amount float64) interface {
			Domain(domain string) interface {
				Fee(fee float64) interface {
					FeeCoveredBy(feeCoveredBy string) interface {
						Product(product string) interface {
							ProductQty(productQty int) PaymentFailedBuilder
						}
					}
				}
			}
		}
	} {
		return &paymentFailedBuilder{
			properties: map[string]any{},
		}
	},
}

type PaymentFailedEvent interface {
	Event
	paymentFailed()
}

type paymentFailedEvent struct {
	baseEvent
}

func (e paymentFailedEvent) paymentFailed() {
}

type PaymentFailedBuilder interface {
	Build() PaymentFailedEvent
	Comment(comment string) PaymentFailedBuilder
	Source(source string) PaymentFailedBuilder
}

type paymentFailedBuilder struct {
	properties map[string]any
}

func (b *paymentFailedBuilder) Amount(amount float64) interface {
	Domain(domain string) interface {
		Fee(fee float64) interface {
			FeeCoveredBy(feeCoveredBy string) interface {
				Product(product string) interface {
					ProductQty(productQty int) PaymentFailedBuilder
				}
			}
		}
	}
} {
	b.properties[`amount`] = amount

	return b
}

func (b *paymentFailedBuilder) Domain(domain string) interface {
	Fee(fee float64) interface {
		FeeCoveredBy(feeCoveredBy string) interface {
			Product(product string) interface {
				ProductQty(productQty int) PaymentFailedBuilder
			}
		}
	}
} {
	b.properties[`domain`] = domain

	return b
}

func (b *paymentFailedBuilder) Fee(fee float64) interface {
	FeeCoveredBy(feeCoveredBy string) interface {
		Product(product string) interface {
			ProductQty(productQty int) PaymentFailedBuilder
		}
	}
} {
	b.properties[`fee`] = fee

	return b
}

func (b *paymentFailedBuilder) FeeCoveredBy(feeCoveredBy string) interface {
	Product(product string) interface {
		ProductQty(productQty int) PaymentFailedBuilder
	}
} {
	b.properties[`feeCoveredBy`] = feeCoveredBy

	return b
}

func (b *paymentFailedBuilder) Product(product string) interface {
	ProductQty(productQty int) PaymentFailedBuilder
} {
	b.properties[`product`] = product

	return b
}

func (b *paymentFailedBuilder) ProductQty(productQty int) PaymentFailedBuilder {
	b.properties[`product_qty`] = productQty

	return b
}

func (b *paymentFailedBuilder) Comment(comment string) PaymentFailedBuilder {
	b.properties[`comment`] = comment

	return b
}

func (b *paymentFailedBuilder) Source(source string) PaymentFailedBuilder {
	b.properties[`source`] = source

	return b
}

func (b *paymentFailedBuilder) Build() PaymentFailedEvent {
	return &paymentFailedEvent{
		newBaseEvent(`paymentFailed`, b.properties),
	}
}

var PaymentSuccess = struct {
	Builder func() interface {
		Price(price float64) interface {
			ProductId(productId string) interface {
				Revenue(revenue float64) interface {
					RevenueType(revenueType string) interface {
						Amount(amount float64) interface {
							Domain(domain string) interface {
								Fee(fee float64) interface {
									FeeCoveredBy(feeCoveredBy string) interface {
										Product(product string) interface {
											ProductQty(productQty int) PaymentSuccessBuilder
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}{
	Builder: func() interface {
		Price(price float64) interface {
			ProductId(productId string) interface {
				Revenue(revenue float64) interface {
					RevenueType(revenueType string) interface {
						Amount(amount float64) interface {
							Domain(domain string) interface {
								Fee(fee float64) interface {
									FeeCoveredBy(feeCoveredBy string) interface {
										Product(product string) interface {
											ProductQty(productQty int) PaymentSuccessBuilder
										}
									}
								}
							}
						}
					}
				}
			}
		}
	} {
		return &paymentSuccessBuilder{
			properties: map[string]any{},
		}
	},
}

type PaymentSuccessEvent interface {
	Event
	paymentSuccess()
}

type paymentSuccessEvent struct {
	baseEvent
}

func (e paymentSuccessEvent) paymentSuccess() {
}

type PaymentSuccessBuilder interface {
	Build() PaymentSuccessEvent
	Quantity(quantity int) PaymentSuccessBuilder
	Comment(comment string) PaymentSuccessBuilder
}

type paymentSuccessBuilder struct {
	properties map[string]any
}

func (b *paymentSuccessBuilder) Price(price float64) interface {
	ProductId(productId string) interface {
		Revenue(revenue float64) interface {
			RevenueType(revenueType string) interface {
				Amount(amount float64) interface {
					Domain(domain string) interface {
						Fee(fee float64) interface {
							FeeCoveredBy(feeCoveredBy string) interface {
								Product(product string) interface {
									ProductQty(productQty int) PaymentSuccessBuilder
								}
							}
						}
					}
				}
			}
		}
	}
} {
	b.properties[`$price`] = price

	return b
}

func (b *paymentSuccessBuilder) ProductId(productId string) interface {
	Revenue(revenue float64) interface {
		RevenueType(revenueType string) interface {
			Amount(amount float64) interface {
				Domain(domain string) interface {
					Fee(fee float64) interface {
						FeeCoveredBy(feeCoveredBy string) interface {
							Product(product string) interface {
								ProductQty(productQty int) PaymentSuccessBuilder
							}
						}
					}
				}
			}
		}
	}
} {
	b.properties[`$productId`] = productId

	return b
}

func (b *paymentSuccessBuilder) Revenue(revenue float64) interface {
	RevenueType(revenueType string) interface {
		Amount(amount float64) interface {
			Domain(domain string) interface {
				Fee(fee float64) interface {
					FeeCoveredBy(feeCoveredBy string) interface {
						Product(product string) interface {
							ProductQty(productQty int) PaymentSuccessBuilder
						}
					}
				}
			}
		}
	}
} {
	b.properties[`$revenue`] = revenue

	return b
}

func (b *paymentSuccessBuilder) RevenueType(revenueType string) interface {
	Amount(amount float64) interface {
		Domain(domain string) interface {
			Fee(fee float64) interface {
				FeeCoveredBy(feeCoveredBy string) interface {
					Product(product string) interface {
						ProductQty(productQty int) PaymentSuccessBuilder
					}
				}
			}
		}
	}
} {
	b.properties[`$revenueType`] = revenueType

	return b
}

func (b *paymentSuccessBuilder) Amount(amount float64) interface {
	Domain(domain string) interface {
		Fee(fee float64) interface {
			FeeCoveredBy(feeCoveredBy string) interface {
				Product(product string) interface {
					ProductQty(productQty int) PaymentSuccessBuilder
				}
			}
		}
	}
} {
	b.properties[`amount`] = amount

	return b
}

func (b *paymentSuccessBuilder) Domain(domain string) interface {
	Fee(fee float64) interface {
		FeeCoveredBy(feeCoveredBy string) interface {
			Product(product string) interface {
				ProductQty(productQty int) PaymentSuccessBuilder
			}
		}
	}
} {
	b.properties[`domain`] = domain

	return b
}

func (b *paymentSuccessBuilder) Fee(fee float64) interface {
	FeeCoveredBy(feeCoveredBy string) interface {
		Product(product string) interface {
			ProductQty(productQty int) PaymentSuccessBuilder
		}
	}
} {
	b.properties[`fee`] = fee

	return b
}

func (b *paymentSuccessBuilder) FeeCoveredBy(feeCoveredBy string) interface {
	Product(product string) interface {
		ProductQty(productQty int) PaymentSuccessBuilder
	}
} {
	b.properties[`feeCoveredBy`] = feeCoveredBy

	return b
}

func (b *paymentSuccessBuilder) Product(product string) interface {
	ProductQty(productQty int) PaymentSuccessBuilder
} {
	b.properties[`product`] = product

	return b
}

func (b *paymentSuccessBuilder) ProductQty(productQty int) PaymentSuccessBuilder {
	b.properties[`product_qty`] = productQty

	return b
}

func (b *paymentSuccessBuilder) Quantity(quantity int) PaymentSuccessBuilder {
	b.properties[`$quantity`] = quantity

	return b
}

func (b *paymentSuccessBuilder) Comment(comment string) PaymentSuccessBuilder {
	b.properties[`comment`] = comment

	return b
}

func (b *paymentSuccessBuilder) Build() PaymentSuccessEvent {
	return &paymentSuccessEvent{
		newBaseEvent(`paymentSuccess`, b.properties),
	}
}

type Ampli struct {
	Disabled bool
	Client   amplitude.Client
	mutex    sync.RWMutex
}

// Load initializes the Ampli wrapper.
// Call once when your application starts.
func (a *Ampli) Load(options LoadOptions) {
	if a.Client != nil {
		log.Print("Warn: Ampli is already initialized. Ampli.Load() should be called once at application start up.")

		return
	}

	var apiKey string
	switch {
	case options.Client.APIKey != "":
		apiKey = options.Client.APIKey
	case options.Environment != "":
		apiKey = APIKey[options.Environment]
	default:
		apiKey = options.Client.Configuration.APIKey
	}

	if apiKey == "" && options.Client.Instance == nil {
		log.Print("Error: Ampli.Load() requires option.Environment, " +
			"and apiKey from either options.Instance.APIKey or APIKey[options.Environment], " +
			"or options.Instance.Instance")
	}

	clientConfig := options.Client.Configuration

	if clientConfig.Plan == nil {
		clientConfig.Plan = &amplitude.Plan{
			Branch:    `main`,
			Source:    `backend`,
			Version:   `2`,
			VersionID: `4fa6851a-4ff0-42f1-b440-8b39f07870e4`,
		}
	}

	if clientConfig.IngestionMetadata == nil {
		clientConfig.IngestionMetadata = &amplitude.IngestionMetadata{
			SourceName:    `go-go-ampli`,
			SourceVersion: `2.0.0`,
		}
	}

	if clientConfig.ServerZone == "" {
		clientConfig.ServerZone = ServerZoneEU
	}

	if options.Client.Instance != nil {
		a.Client = options.Client.Instance
	} else {
		clientConfig.APIKey = apiKey
		a.Client = amplitude.NewClient(clientConfig)
	}

	a.mutex.Lock()
	a.Disabled = options.Disabled
	a.mutex.Unlock()
}

// InitializedAndEnabled checks if Ampli is initialized and enabled.
func (a *Ampli) InitializedAndEnabled() bool {
	if a.Client == nil {
		log.Print("Error: Ampli is not yet initialized. Have you called Ampli.Load() on app start?")

		return false
	}

	a.mutex.RLock()
	defer a.mutex.RUnlock()

	return !a.Disabled
}

func (a *Ampli) setUserID(userID string, eventOptions *EventOptions) {
	if userID != "" {
		eventOptions.UserID = userID
	}
}

// Track tracks an event.
func (a *Ampli) Track(userID string, event Event, eventOptions ...EventOptions) {
	if !a.InitializedAndEnabled() {
		return
	}

	var options EventOptions
	if len(eventOptions) > 0 {
		options = eventOptions[0]
	}

	a.setUserID(userID, &options)

	baseEvent := event.ToAmplitudeEvent()
	baseEvent.EventOptions = options

	a.Client.Track(baseEvent)
}

// Identify identifies a user and set user properties.
func (a *Ampli) Identify(userID string, eventOptions ...EventOptions) {
	identify := newBaseEvent(IdentifyEventType, nil)
	a.Track(userID, identify, eventOptions...)
}

// Flush flushes events waiting in buffer.
func (a *Ampli) Flush() {
	if !a.InitializedAndEnabled() {
		return
	}

	a.Client.Flush()
}

// Shutdown disables and shutdowns Ampli Instance.
func (a *Ampli) Shutdown() {
	if !a.InitializedAndEnabled() {
		return
	}

	a.mutex.Lock()
	a.Disabled = true
	a.mutex.Unlock()

	a.Client.Shutdown()
}

func (a *Ampli) EmailOpened(userID string, event EmailOpenedEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}

func (a *Ampli) EmailSent(userID string, event EmailSentEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}

func (a *Ampli) PaymentFailed(userID string, event PaymentFailedEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}

func (a *Ampli) PaymentSuccess(userID string, event PaymentSuccessEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}
