package pipeline

import (
	"context"
	"reflect"
	"time"

	"github.com/grafana/grafana-plugin-sdk-go/data"
)

type ChangeLogOutputConfig struct {
	FieldName string `json:"fieldName"`
	Channel   string `json:"channel"`
}

// ChangeLogFrameOutput can monitor value changes of the specified field and output
// special change frame to the configured channel.
type ChangeLogFrameOutput struct {
	frameStorage FrameGetSetter
	config       ChangeLogOutputConfig
}

func NewChangeLogFrameOutput(frameStorage FrameGetSetter, config ChangeLogOutputConfig) *ChangeLogFrameOutput {
	return &ChangeLogFrameOutput{frameStorage: frameStorage, config: config}
}

const FrameOutputTypeChangeLog = "changeLog"

func (out *ChangeLogFrameOutput) Type() string {
	return FrameOutputTypeChangeLog
}

func (out *ChangeLogFrameOutput) OutputFrame(_ context.Context, vars Vars, frame *data.Frame) ([]*ChannelFrame, error) {
	previousFrame, previousFrameOK, err := out.frameStorage.Get(vars.OrgID, out.config.Channel)
	if err != nil {
		return nil, err
	}

	fieldName := out.config.FieldName

	previousFrameFieldIndex := -1
	if previousFrameOK {
		for i, f := range previousFrame.Fields {
			if f.Name == fieldName {
				previousFrameFieldIndex = i
			}
		}
	}

	currentFrameFieldIndex := -1
	for i, f := range frame.Fields {
		if f.Name == fieldName {
			currentFrameFieldIndex = i
		}
	}

	var previousValue any
	if previousFrameFieldIndex >= 0 {
		// Take last value for the field.
		previousValue = previousFrame.Fields[previousFrameFieldIndex].At(previousFrame.Fields[previousFrameFieldIndex].Len() - 1)
	}

	fTime := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
	fTime.Name = "time"
	f1 := data.NewFieldFromFieldType(frame.Fields[currentFrameFieldIndex].Type(), 0)
	f1.Name = "old"
	f2 := data.NewFieldFromFieldType(frame.Fields[currentFrameFieldIndex].Type(), 0)
	f2.Name = "new"

	if currentFrameFieldIndex >= 0 {
		for i := 0; i < frame.Fields[currentFrameFieldIndex].Len(); i++ {
			currentValue := frame.Fields[currentFrameFieldIndex].At(i)
			if !reflect.DeepEqual(
				previousValue,
				currentValue,
			) {
				fTime.Append(time.Now())
				f1.Append(previousValue)
				f2.Append(currentValue)
				previousValue = currentValue
			}
		}
	}

	if fTime.Len() > 0 {
		changeFrame := data.NewFrame("change", fTime, f1, f2)
		err := out.frameStorage.Set(vars.OrgID, out.config.Channel, frame)
		if err != nil {
			return nil, err
		}
		return []*ChannelFrame{{
			Channel: out.config.Channel,
			Frame:   changeFrame,
		}}, nil
	}

	return nil, out.frameStorage.Set(vars.OrgID, out.config.Channel, frame)
}
