package runtimevar

import (
	"context"
	"errors"
	"fmt"

	"go-micro.dev/v4/config/source"
	"gocloud.dev/runtimevar"
)

type watcher struct {
	name string
	opts source.Options
	exit chan bool
	v    *runtimevar.Variable
}

func (w *watcher) Next() (*source.ChangeSet, error) {
	// check exit status
	select {
	case <-w.exit:
		return nil, errors.New("watcher stopped")
	default:
	}

	// create a context with cancellation to kill watcher
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		// if the watcher stops then cancel
		select {
		case <-w.exit:
			cancel()
		}
	}()

	s, err := w.v.Watch(ctx)
	if err != nil {
		return nil, err
	}

	// assuming value is bytes
	b, err := w.opts.Encoder.Encode(s.Value.([]byte))
	if err != nil {
		return nil, fmt.Errorf("error reading source: %v", err)
	}

	cs := &source.ChangeSet{
		Timestamp: s.UpdateTime,
		Format:    w.opts.Encoder.String(),
		Source:    w.name,
		Data:      b,
	}
	cs.Checksum = cs.Sum()

	return cs, nil
}

func (w *watcher) Stop() error {
	select {
	case <-w.exit:
		return nil
	default:
		close(w.exit)
		return nil
	}
}

func newWatcher(name string, v *runtimevar.Variable, opts source.Options) (source.Watcher, error) {
	return &watcher{
		name: name,
		opts: opts,
		exit: make(chan bool),
		v:    v,
	}, nil
}