package prometheus

import (
	"context"
	"fmt"
	"time"

	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/tsdb"

	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/vmctlutil"
)

// Config contains a list of params needed
// for reading Prometheus snapshots
type Config struct {
	// Path to snapshot directory
	Snapshot string

	Filter       Filter
	TemporaryDir string
}

// Filter contains configuration for filtering
// the timeseries
type Filter struct {
	TimeMin    string
	TimeMax    string
	Label      string
	LabelValue string
}

// Client is a wrapper over Prometheus tsdb.DBReader
type Client struct {
	*tsdb.DBReadOnly
	filter filter
}

type filter struct {
	min, max   int64
	label      string
	labelValue string
}

func (f filter) inRange(minV, maxV int64) bool {
	fmin, fmax := f.min, f.max
	if minV == 0 {
		fmin = minV
	}
	if fmax == 0 {
		fmax = maxV
	}
	return minV <= fmax && fmin <= maxV
}

// NewClient creates and validates new Client
// with given Config
func NewClient(cfg Config) (*Client, error) {
	db, err := tsdb.OpenDBReadOnly(cfg.Snapshot, cfg.TemporaryDir, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to open snapshot %q: %s", cfg.Snapshot, err)
	}
	c := &Client{DBReadOnly: db}
	timeMin, timeMax, err := parseTime(cfg.Filter.TimeMin, cfg.Filter.TimeMax)
	if err != nil {
		return nil, fmt.Errorf("failed to parse time in filter: %s", err)
	}
	c.filter = filter{
		min:        timeMin,
		max:        timeMax,
		label:      cfg.Filter.Label,
		labelValue: cfg.Filter.LabelValue,
	}
	return c, nil
}

// Explore fetches all available blocks from a snapshot
// and collects the Meta() data from each block.
// Explore does initial filtering by time-range
// for snapshot blocks but does not take into account
// label filters.
func (c *Client) Explore() ([]tsdb.BlockReader, error) {
	blocks, err := c.Blocks()
	if err != nil {
		return nil, fmt.Errorf("failed to fetch blocks: %s", err)
	}
	s := &vmctlutil.Stats{
		Filtered: c.filter.min != 0 || c.filter.max != 0 || c.filter.label != "",
		Blocks:   len(blocks),
	}
	var blocksToImport []tsdb.BlockReader
	for _, block := range blocks {
		meta := block.Meta()
		if !c.filter.inRange(meta.MinTime, meta.MaxTime) {
			s.SkippedBlocks++
			continue
		}
		if s.MinTime == 0 || meta.MinTime < s.MinTime {
			s.MinTime = meta.MinTime
		}
		if s.MaxTime == 0 || meta.MaxTime > s.MaxTime {
			s.MaxTime = meta.MaxTime
		}
		s.Samples += meta.Stats.NumSamples
		s.Series += meta.Stats.NumSeries
		blocksToImport = append(blocksToImport, block)
	}
	fmt.Println(s)
	return blocksToImport, nil
}

// CloseableSeriesSet defines a SeriesSet with Close method
type CloseableSeriesSet struct {
	SeriesSet storage.SeriesSet
	Close     func() error
}

// Read reads the given BlockReader according to configured
// time and label filters.
func (c *Client) Read(ctx context.Context, block tsdb.BlockReader) (*CloseableSeriesSet, error) {
	minTime, maxTime := block.Meta().MinTime, block.Meta().MaxTime
	if c.filter.min != 0 {
		minTime = c.filter.min
	}
	if c.filter.max != 0 {
		maxTime = c.filter.max
	}
	q, err := tsdb.NewBlockQuerier(block, minTime, maxTime)
	if err != nil {
		return nil, err
	}
	ss := q.Select(ctx, false, nil, labels.MustNewMatcher(labels.MatchRegexp, c.filter.label, c.filter.labelValue))
	return &CloseableSeriesSet{ss, q.Close}, nil
}

func parseTime(start, end string) (int64, int64, error) {
	var s, e int64
	if start == "" && end == "" {
		return 0, 0, nil
	}
	if start != "" {
		v, err := time.Parse(time.RFC3339, start)
		if err != nil {
			return 0, 0, fmt.Errorf("failed to parse %q: %s", start, err)
		}
		s = v.UnixNano() / int64(time.Millisecond)
	}
	if end != "" {
		v, err := time.Parse(time.RFC3339, end)
		if err != nil {
			return 0, 0, fmt.Errorf("failed to parse %q: %s", end, err)
		}
		e = v.UnixNano() / int64(time.Millisecond)
	}
	return s, e, nil
}
