package v2

import (
	"time"

	"github.com/smartcontractkit/chainlink-common/pkg/logger"
	"github.com/smartcontractkit/chainlink-common/pkg/workflows/dontime"
)

type TimeProvider interface {
	GetNodeTime() time.Time
	GetDONTime() (time.Time, error)
}

var _ TimeProvider = &DonTimeProvider{}

type DonTimeProvider struct {
	workflowExecutionID string
	timeSeqNum          int
	donTimeStore        *dontime.Store
	lggr                logger.Logger
}

func NewDonTimeProvider(store *dontime.Store, workflowExecutionID string, lggr logger.Logger) TimeProvider {
	return &DonTimeProvider{
		workflowExecutionID: workflowExecutionID,
		timeSeqNum:          0,
		donTimeStore:        store,
		lggr:                logger.Named(lggr, "TimeProvider"),
	}
}

func (tp *DonTimeProvider) GetNodeTime() time.Time {
	return fromUnixMilli(tp.donTimeStore.GetLastObservedDonTime())
}

// GetDONTime makes a request to the WorkflowLib plugin store for DON Time
func (tp *DonTimeProvider) GetDONTime() (time.Time, error) {
	defer func() {
		tp.timeSeqNum++
	}()

	donTimeResp := <-tp.donTimeStore.RequestDonTime(tp.workflowExecutionID, tp.timeSeqNum)
	if donTimeResp.Err != nil {
		// This node's request timed out, so it did not include the request in its observation.
		// However, consensus may still have been reached if other nodes included the request.
		if donTime := tp.donTimeStore.GetDonTimeForSeqNum(tp.workflowExecutionID, tp.timeSeqNum); donTime != nil {
			// Consensus was reached; return the DON time generated by the network.
			return fromUnixMilli(*donTime), nil
		}
		tp.lggr.Errorf("No DON time reached for time call sequence %d on executionID %s; returning local node time as fallback. "+
			"This may result in non-deterministic behavior across nodes for this workflow step", tp.timeSeqNum, tp.workflowExecutionID)
		return tp.GetNodeTime(), nil
	}
	return fromUnixMilli(donTimeResp.Timestamp), nil
}

func fromUnixMilli(ms int64) time.Time {
	return time.Unix(0, ms*int64(time.Millisecond)).UTC()
}
