package integration

import (
	"sync"
	"fmt"
	"encoding/json"

	"openreplay/backend/pkg/messages"
	"openreplay/backend/pkg/db/postgres"
	"openreplay/backend/pkg/utime"
)

const MAX_ATTEMPTS_IN_A_ROW = 4
const MAX_ATTEMPTS = 40
const ATTEMPTS_INTERVAL = 3 * 60 * 60 * 1000

type requester interface {
	Request(*client) error
}

type requestData struct {
	LastMessageTimestamp uint64 // `json:"lastMessageTimestamp, string"`
	LastMessageId string
	UnsuccessfullAttemptsCount int
	LastAttemptTimestamp int64
}

type client struct {
	requestData
	requester
	integration *postgres.Integration
	// TODO: timeout ?
	mux sync.Mutex
	updateChan chan<- postgres.Integration
	evChan chan<- *SessionErrorEvent
	errChan chan<- error
}

type SessionErrorEvent struct {
	SessionID uint64
	Token string
	*messages.RawErrorEvent
}

type ClientMap map[ string ]*client

func NewClient(i *postgres.Integration, updateChan chan<- postgres.Integration, evChan chan<- *SessionErrorEvent, errChan chan<- error) (*client, error) {
	c := new(client)
	if err := c.Update(i); err != nil {
		return nil, err
	}

	if err := json.Unmarshal(i.RequestData, &c.requestData); err != nil {
		return nil, err
	}
	c.evChan = evChan
	c.errChan = errChan
	c.updateChan = updateChan
	// TODO: RequestData manager
	if c.requestData.LastMessageTimestamp == 0 {
		// ?
		c.requestData.LastMessageTimestamp = uint64(utime.CurrentTimestamp() - 24*60*60*1000)
	}

	return c, nil
}


// from outside
func (c* client) Update(i *postgres.Integration) error {
	c.mux.Lock()
	defer c.mux.Unlock()
	var r requester
	switch i.Provider {
	case "bugsnag":
		r = new(bugsnag)
	case "cloudwatch":
		r = new(cloudwatch)
	case "datadog":
		r = new(datadog)
	case "elasticsearch":
		r = new(elasticsearch)
	case "newrelic":
		r = new(newrelic)
	case "rollbar":
		r = new(rollbar)
	case "sentry":
		r = new(sentry)
	case "stackdriver":
		r = new(stackdriver)
	case "sumologic":
		r = new(sumologic)
	}
	if err := json.Unmarshal(i.Options, r); err != nil {
		return err
	}
	c.integration = i
	c.requester = r
	return nil
}

// package scope
func (c *client) setLastMessageTimestamp(timestamp uint64) {
	if timestamp > c.requestData.LastMessageTimestamp {
		c.requestData.LastMessageTimestamp = timestamp
	}
}
func (c *client) getLastMessageTimestamp() uint64 {
	return c.requestData.LastMessageTimestamp
}
func (c *client) setLastMessageId(timestamp uint64, id string) {
	//if timestamp >= c.requestData.LastMessageTimestamp {
		c.requestData.LastMessageId = id
		c.requestData.LastMessageTimestamp = timestamp
	//}
}
func (c *client) getLastMessageId() string {
	return c.requestData.LastMessageId
}

func (c *client) handleError(err error) {
	c.errChan <- fmt.Errorf("%v | Integration: %v", err, *c.integration)
}

// Thread-safe
func (c *client) Request() {
	c.mux.Lock()
	defer c.mux.Unlock()
	if c.requestData.UnsuccessfullAttemptsCount >= MAX_ATTEMPTS ||
			(c.requestData.UnsuccessfullAttemptsCount >= MAX_ATTEMPTS_IN_A_ROW &&
			utime.CurrentTimestamp() - c.requestData.LastAttemptTimestamp < ATTEMPTS_INTERVAL) {
		return
	}

	c.requestData.LastAttemptTimestamp = utime.CurrentTimestamp()
	err := c.requester.Request(c)
	if err != nil {
		c.handleError(err)
		c.requestData.UnsuccessfullAttemptsCount++;
	} else {
		c.requestData.UnsuccessfullAttemptsCount = 0
	}
	rd, err := json.Marshal(c.requestData)
	if err != nil {
		c.handleError(err)
	}
	// RequestData is a byte array (pointer-like type), but it's replacement
	// won't affect the previous value sent by channel
	c.integration.RequestData = rd
	c.updateChan <- *c.integration
}


