package resolver

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/services/datasources"
	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
	"github.com/grafana/grafana/pkg/tsdb/grafanads"
)

type dsVal struct {
	InternalID   int64
	IsDefault    bool
	Name         string
	Type         string
	UID          string
	PluginExists bool // type exists
}

type dsCache struct {
	ds          datasources.DataSourceService
	pluginStore pluginstore.Store
	cache       map[int64]map[string]*dsVal
	timestamp   time.Time // across all orgIDs
	mu          sync.Mutex
}

func (c *dsCache) refreshCache(ctx context.Context) error {
	old := c.timestamp

	c.mu.Lock()
	defer c.mu.Unlock()

	if c.timestamp != old {
		return nil // already updated while we waited!
	}

	cache := make(map[int64]map[string]*dsVal, 0)
	defaultDS := make(map[int64]*dsVal, 0)

	q := &datasources.GetAllDataSourcesQuery{}
	dsList, err := c.ds.GetAllDataSources(ctx, q)
	if err != nil {
		return err
	}

	for _, ds := range dsList {
		val := &dsVal{
			InternalID: ds.ID,
			Name:       ds.Name,
			UID:        ds.UID,
			Type:       ds.Type,
			IsDefault:  ds.IsDefault,
		}
		_, ok := c.pluginStore.Plugin(ctx, val.Type)
		val.PluginExists = ok

		orgCache, ok := cache[ds.OrgID]
		if !ok {
			orgCache = make(map[string]*dsVal, 0)
			cache[ds.OrgID] = orgCache
		}

		orgCache[val.UID] = val

		// Empty string or
		if val.IsDefault {
			defaultDS[ds.OrgID] = val
		}
	}

	for orgID, orgDSCache := range cache {
		// modifies the cache we are iterating over?
		for _, ds := range orgDSCache {
			// Lookup by internal ID
			id := fmt.Sprintf("%d", ds.InternalID)
			_, ok := orgDSCache[id]
			if !ok {
				orgDSCache[id] = ds
			}

			// Lookup by name
			_, ok = orgDSCache[ds.Name]
			if !ok {
				orgDSCache[ds.Name] = ds
			}
		}

		// Register the internal builtin grafana datasource
		gds := &dsVal{
			Name:         grafanads.DatasourceName,
			UID:          grafanads.DatasourceUID,
			Type:         grafanads.DatasourceUID,
			PluginExists: true,
		}
		orgDSCache[gds.UID] = gds
		ds, ok := defaultDS[orgID]
		if !ok {
			ds = gds // use the internal grafana datasource
		}
		orgDSCache[""] = ds
		if orgDSCache["default"] == nil {
			orgDSCache["default"] = ds
		}
	}

	c.cache = cache
	c.timestamp = getNow()
	return nil
}

func (c *dsCache) getDS(ctx context.Context, uid string) (*dsVal, error) {
	// refresh cache every 1 min
	if c.cache == nil || c.timestamp.Before(getNow().Add(time.Minute*-1)) {
		err := c.refreshCache(ctx)
		if err != nil {
			return nil, err
		}
	}

	usr, err := identity.GetRequester(ctx)
	if err != nil {
		return nil, nil // no user
	}

	v, ok := c.cache[usr.GetOrgID()]
	if !ok {
		return nil, nil // org not found
	}
	ds, ok := v[uid]
	if !ok {
		return nil, nil // data source not found
	}
	return ds, nil
}
