package promql

import (
	"context"
	"fmt"
	"time"

	"github.com/go-kit/log"
	pmodel "github.com/prometheus/common/model"
	"github.com/prometheus/common/promlog"
	plog "github.com/prometheus/common/promlog"
	pconfig "github.com/prometheus/prometheus/config"
	pql "github.com/prometheus/prometheus/promql"
	pstorage "github.com/prometheus/prometheus/storage"
	premote "github.com/prometheus/prometheus/storage/remote"
	"go.signoz.io/signoz/pkg/query-service/interfaces"
)

type PqlEngine struct {
	engine        *pql.Engine
	fanoutStorage pstorage.Storage
}

func FromConfigPath(promConfigPath string) (*PqlEngine, error) {
	// load storage path
	c, err := pconfig.LoadFile(promConfigPath, false, false, nil)
	if err != nil {
		return nil, fmt.Errorf("couldn't load configuration (--config.file=%q): %v", promConfigPath, err)
	}

	return NewPqlEngine(c)
}

func FromReader(ch interfaces.Reader) (*PqlEngine, error) {
	return &PqlEngine{
		engine:        ch.GetQueryEngine(),
		fanoutStorage: *ch.GetFanoutStorage(),
	}, nil
}

func NewPqlEngine(config *pconfig.Config) (*PqlEngine, error) {

	logLevel := plog.AllowedLevel{}
	logLevel.Set("debug")

	allowedFormat := promlog.AllowedFormat{}
	allowedFormat.Set("logfmt")

	promlogConfig := promlog.Config{
		Level:  &logLevel,
		Format: &allowedFormat,
	}

	logger := plog.New(&promlogConfig)

	opts := pql.EngineOpts{
		Logger:     log.With(logger, "component", "promql evaluator"),
		Reg:        nil,
		MaxSamples: 50000000,
		Timeout:    time.Duration(2 * time.Minute),
		ActiveQueryTracker: pql.NewActiveQueryTracker(
			"",
			20,
			logger,
		),
	}

	e := pql.NewEngine(opts)
	startTime := func() (int64, error) {
		return int64(pmodel.Latest), nil
	}

	remoteStorage := premote.NewStorage(
		log.With(logger, "component", "remote"),
		nil,
		startTime,
		"",
		time.Duration(1*time.Minute),
		nil,
	)
	fanoutStorage := pstorage.NewFanout(logger, remoteStorage)

	remoteStorage.ApplyConfig(config)

	return &PqlEngine{
		engine:        e,
		fanoutStorage: fanoutStorage,
	}, nil
}

func (p *PqlEngine) RunAlertQuery(ctx context.Context, qs string, start, end time.Time, interval time.Duration) (pql.Matrix, error) {
	q, err := p.engine.NewRangeQuery(ctx, p.fanoutStorage, nil, qs, start, end, interval)
	if err != nil {
		return nil, err
	}

	res := q.Exec(ctx)

	if res.Err != nil {
		return nil, res.Err
	}

	switch typ := res.Value.(type) {
	case pql.Vector:
		series := make([]pql.Series, 0, len(typ))
		value := res.Value.(pql.Vector)
		for _, smpl := range value {
			series = append(series, pql.Series{
				Metric: smpl.Metric,
				Floats: []pql.FPoint{{T: smpl.T, F: smpl.F}},
			})
		}
		return series, nil
	case pql.Scalar:
		value := res.Value.(pql.Scalar)
		series := make([]pql.Series, 0, 1)
		series = append(series, pql.Series{
			Floats: []pql.FPoint{{T: value.T, F: value.V}},
		})
		return series, nil
	case pql.Matrix:
		return res.Value.(pql.Matrix), nil
	default:
		return nil, fmt.Errorf("rule result is not a vector or scalar")
	}
}
