package generate_datasources

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"sync"

	"github.com/grafana/grafana/pkg/expr"
)

var grafanaDatasources = []string{expr.DatasourceType, "datasource"}

// there are some datasources that have backend flag as true but don't call /ds/query endpoint
// through DataSourceWithBackend class
var unsupportedDataSourcesMap = map[string]bool{}

type listPluginResponse struct {
	Items []struct {
		Slug string `json:"slug"`
	} `json:"items"`
}

type datasourceDetails struct {
	Slug     string `json:"slug"`
	Backend  bool   `json:"backend"`
	Alerting bool   `json:"alerting"`
}

type datasource struct {
	Json datasourceDetails `json:"json"`
}

// Get the slugs of all datasource plugins that are compatible with public dashboards
func GetCompatibleDatasources(baseUrl string) ([]string, error) {
	slugs, err := getDatasourcePluginSlugs(baseUrl)
	if err != nil {
		return nil, err
	}

	datasources := getDatasourceDetails(slugs, baseUrl)

	// we only consider a datasource to be supported when alerting and backend are both true
	var supported []string
	for _, datasource := range datasources {
		if datasource.Alerting && datasource.Backend && !unsupportedDataSourcesMap[datasource.Slug] {
			supported = append(supported, datasource.Slug)
		}
	}

	supported = append(supported, grafanaDatasources...)

	sort.Strings(supported)
	return supported, nil
}

// Get a list of all the datasource plugin slugs
func getDatasourcePluginSlugs(baseUrl string) ([]string, error) {
	resp, err := http.Get(baseUrl + allPluginsEndpoint())
	if err != nil {
		return nil, err
	}
	res := &listPluginResponse{}
	err = json.NewDecoder(resp.Body).Decode(res)
	if err != nil {
		return nil, err
	}
	slugs := make([]string, 0, len(res.Items))
	for _, meta := range res.Items {
		slugs = append(slugs, meta.Slug)
	}
	_ = resp.Body.Close()
	return slugs, nil
}

// Get the details for each datasource plugin by its slug
func getDatasourceDetails(slugs []string, baseUrl string) []datasourceDetails {
	datasources := make([]datasourceDetails, len(slugs))
	var wg sync.WaitGroup
	for i, slug := range slugs {
		wg.Add(1)
		// create new goroutine for each outgoing request
		go func(i int, slug string) {
			defer wg.Done()
			url := baseUrl + pluginEndpoint(slug)
			// url can not be constant since it gets generated based on slug
			//nolint:gosec
			r, err := http.Get(url)
			if err != nil {
				panic(err)
			}

			datasource := &datasource{}
			err = json.NewDecoder(r.Body).Decode(datasource)
			if err != nil {
				panic(err)
			}
			datasource.Json.Slug = slug
			datasources[i] = datasource.Json
			_ = r.Body.Close()
		}(i, slug)
	}

	wg.Wait()

	return datasources
}

func pluginEndpoint(slug string) string {
	return fmt.Sprintf("/api/plugins/%s?version=latest", slug)
}

func allPluginsEndpoint() string {
	return "/api/plugins?orderBy=weight&direction=asc&typeCodeIn[]=datasource"
}
