package resolver

import (
	"context"
	"fmt"
	"time"

	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/datasources"
	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
	"github.com/grafana/grafana/pkg/services/store/entity"
)

const (
	WarningNotImplemented           = "not implemented"
	WarningDatasourcePluginNotFound = "datasource plugin not found"
	WarningTypeNotSpecified         = "type not specified"
	WarningPluginNotFound           = "plugin not found"
)

// for testing
var getNow = func() time.Time { return time.Now() }

type ResolutionInfo struct {
	OK        bool      `json:"ok"`
	Key       string    `json:"key,omitempty"`  // k8s key
	Warning   string    `json:"kind,omitempty"` // old syntax?  (name>uid) references a renamed object?
	Timestamp time.Time `json:"timestamp,omitempty"`
}

type EntityReferenceResolver interface {
	Resolve(ctx context.Context, ref *entity.EntityExternalReference) (ResolutionInfo, error)
}

func ProvideEntityReferenceResolver(ds datasources.DataSourceService, pluginStore pluginstore.Store) EntityReferenceResolver {
	return &standardReferenceResolver{
		pluginStore: pluginStore,
		ds: dsCache{
			ds:          ds,
			pluginStore: pluginStore,
		},
	}
}

type standardReferenceResolver struct {
	pluginStore pluginstore.Store
	ds          dsCache
}

func (r *standardReferenceResolver) Resolve(ctx context.Context, ref *entity.EntityExternalReference) (ResolutionInfo, error) {
	if ref == nil {
		return ResolutionInfo{OK: false, Timestamp: getNow()}, fmt.Errorf("ref is nil")
	}

	switch ref.Family {
	case entity.StandardKindDataSource:
		return r.resolveDatasource(ctx, ref)

	case entity.ExternalEntityReferencePlugin:
		return r.resolvePlugin(ctx, ref)

		// case entity.ExternalEntityReferenceRuntime:
		// 	return ResolutionInfo{
		// 		OK:        false,
		// 		Timestamp: getNow(),
		// 		Warning:   WarningNotImplemented,
		// 	}, nil
	}

	return ResolutionInfo{
		OK:        false,
		Timestamp: getNow(),
		Warning:   WarningNotImplemented,
	}, nil
}

func (r *standardReferenceResolver) resolveDatasource(ctx context.Context, ref *entity.EntityExternalReference) (ResolutionInfo, error) {
	ds, err := r.ds.getDS(ctx, ref.Identifier)
	if err != nil || ds == nil || ds.UID == "" {
		return ResolutionInfo{
			OK:        false,
			Timestamp: r.ds.timestamp,
		}, err
	}

	res := ResolutionInfo{
		OK:        true,
		Timestamp: r.ds.timestamp,
		Key:       ds.UID, // TODO!
	}
	if !ds.PluginExists {
		res.OK = false
		res.Warning = WarningDatasourcePluginNotFound
	} else if ref.Type == "" {
		ref.Type = ds.Type // awkward! but makes the reporting accurate for dashboards before schemaVersion 36
		res.Warning = WarningTypeNotSpecified
	} else if ref.Type != ds.Type {
		res.Warning = fmt.Sprintf("type mismatch (expect:%s, found:%s)", ref.Type, ds.Type)
	}
	return res, nil
}

func (r *standardReferenceResolver) resolvePlugin(ctx context.Context, ref *entity.EntityExternalReference) (ResolutionInfo, error) {
	p, ok := r.pluginStore.Plugin(ctx, ref.Identifier)
	if !ok {
		return ResolutionInfo{
			OK:        false,
			Timestamp: getNow(),
			Warning:   WarningPluginNotFound,
		}, nil
	}

	if p.Type != plugins.Type(ref.Type) {
		return ResolutionInfo{
			OK:        false,
			Timestamp: getNow(),
			Warning:   fmt.Sprintf("expected type: %s, found%s", ref.Type, p.Type),
		}, nil
	}

	return ResolutionInfo{
		OK:        true,
		Timestamp: getNow(),
	}, nil
}
