package flux

import (
	"context"
	"fmt"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/influxdata/influxdb-client-go/v2/api"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)

var (
	glog = log.New("tsdb.influx_flux")
)

// Query builds flux queries, executes them, and returns the results.
func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
	logger := glog.FromContext(ctx)
	tRes := backend.NewQueryDataResponse()
	logger.Debug("Received a query", "query", tsdbQuery)
	r, err := runnerFromDataSource(dsInfo)
	if err != nil {
		return &backend.QueryDataResponse{}, err
	}
	defer r.client.Close()

	for _, query := range tsdbQuery.Queries {
		qm, err := getQueryModel(query, query.TimeRange, dsInfo)
		if err != nil {
			tRes.Responses[query.RefID] = backend.DataResponse{
				Error:       err,
				ErrorSource: backend.ErrorSourceDownstream,
			}
			continue
		}

		// If the default changes also update labels/placeholder in config page.
		maxSeries := dsInfo.MaxSeries
		res := executeQuery(ctx, logger, *qm, r, maxSeries)

		tRes.Responses[query.RefID] = res
	}
	return tRes, nil
}

// runner is an influxdb2 Client with an attached org property and is used
// for running flux queries.
type runner struct {
	client influxdb2.Client
	org    string
}

// This is an interface to help testing
type queryRunner interface {
	runQuery(ctx context.Context, q string) (*api.QueryTableResult, error)
}

// runQuery executes fluxQuery against the Runner's organization and returns a Flux typed result.
func (r *runner) runQuery(ctx context.Context, fluxQuery string) (*api.QueryTableResult, error) {
	qa := r.client.QueryAPI(r.org)
	return qa.Query(ctx, fluxQuery)
}

// runnerFromDataSource creates a runner from the datasource model (the datasource instance's configuration).
func runnerFromDataSource(dsInfo *models.DatasourceInfo) (*runner, error) {
	org := dsInfo.Organization
	if org == "" {
		return nil, fmt.Errorf("missing organization in datasource configuration")
	}

	url := dsInfo.URL
	if url == "" {
		return nil, fmt.Errorf("missing URL from datasource configuration")
	}
	opts := influxdb2.DefaultOptions()
	opts.HTTPOptions().SetHTTPClient(dsInfo.HTTPClient)
	opts.SetHTTPRequestTimeout(uint(dsInfo.Timeout.Seconds()))
	return &runner{
		client: influxdb2.NewClientWithOptions(url, dsInfo.Token, opts),
		org:    org,
	}, nil
}
