package provider

import (
	"context"
	"os"
	"regexp"
	"sort"
	"time"

	log "github.com/go-pkgz/lgr"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v3"

	"github.com/umputun/reproxy/app/discovery"
)

// File implements file-based provider, defined with yaml file
type File struct {
	FileName      string
	CheckInterval time.Duration
	Delay         time.Duration
}

// Events returns channel updating on file change only
func (d *File) Events(ctx context.Context) <-chan struct{} {
	res := make(chan struct{})

	// no need to queue multiple events
	trySubmit := func(ch chan struct{}) {
		select {
		case ch <- struct{}{}:
		default:
		}
	}

	go func() {
		tk := time.NewTicker(d.CheckInterval)
		lastModif := time.Time{}
		for {
			select {
			case <-tk.C:
				fi, err := os.Stat(d.FileName)
				if err != nil {
					continue
				}
				if fi.ModTime() != lastModif {
					// don't react on modification right away
					if fi.ModTime().Sub(lastModif) < d.Delay {
						continue
					}
					log.Printf("[DEBUG] file %s changed, %s -> %s", d.FileName,
						lastModif.Format(time.RFC3339Nano), fi.ModTime().Format(time.RFC3339Nano))
					lastModif = fi.ModTime()
					trySubmit(res)
				}
			case <-ctx.Done():
				close(res)
				tk.Stop()
				return
			}
		}
	}()
	return res
}

// List all src dst pairs
func (d *File) List() (res []discovery.UrlMapper, err error) {

	var fileConf map[string][]struct {
		SourceRoute string `yaml:"route"`
		Dest        string `yaml:"dest"`
		Ping        string `yaml:"ping"`
	}
	fh, err := os.Open(d.FileName)
	if err != nil {
		return nil, errors.Wrapf(err, "can't open %s", d.FileName)
	}
	defer fh.Close()

	if err = yaml.NewDecoder(fh).Decode(&fileConf); err != nil {
		return nil, errors.Wrapf(err, "can't parse %s", d.FileName)
	}
	log.Printf("[DEBUG] file provider %+v", res)

	for srv, fl := range fileConf {
		for _, f := range fl {
			rx, err := regexp.Compile(f.SourceRoute)
			if err != nil {
				return nil, errors.Wrapf(err, "can't parse regex %s", f.SourceRoute)
			}
			if srv == "default" {
				srv = "*"
			}
			mapper := discovery.UrlMapper{Server: srv, SrcMatch: *rx, Dst: f.Dest, PingURL: f.Ping}
			res = append(res, mapper)
		}
	}
	sort.Slice(res, func(i, j int) bool {
		if res[i].Server == res[j].Server {
			return res[i].SrcMatch.String() < res[j].SrcMatch.String()
		}
		return res[i].Server < res[j].Server
	})
	return res, nil
}

func (d *File) ID() discovery.ProviderID { return discovery.PIFile }
