package service

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"time"

	"github.com/google/uuid"
	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
	"go.opentelemetry.io/otel"

	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/expr"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/metrics"
	"github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/annotations"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	"github.com/grafana/grafana/pkg/services/licensing"
	"github.com/grafana/grafana/pkg/services/publicdashboards"
	. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
	"github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2"
	"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
	"github.com/grafana/grafana/pkg/services/query"
	"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

// PublicDashboardServiceImpl Define the Service Implementation. We're generating mock implementation
// automatically
type PublicDashboardServiceImpl struct {
	log                log.Logger
	cfg                *setting.Cfg
	features           featuremgmt.FeatureToggles
	store              publicdashboards.Store
	intervalCalculator intervalv2.Calculator
	QueryDataService   query.Service
	AnnotationsRepo    annotations.Repository
	ac                 accesscontrol.AccessControl
	serviceWrapper     publicdashboards.ServiceWrapper
	dashboardService   dashboards.DashboardService
	license            licensing.Licensing
}

var LogPrefix = "publicdashboards.service"
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/publicdashboards/service")

// Gives us compile time error if the service does not adhere to the contract of
// the interface
var _ publicdashboards.Service = (*PublicDashboardServiceImpl)(nil)

// ProvideService Factory for method used by wire to inject dependencies.
// builds the service, and api, and configures routes
func ProvideService(
	cfg *setting.Cfg,
	features featuremgmt.FeatureToggles,
	store publicdashboards.Store,
	qds query.Service,
	anno annotations.Repository,
	ac accesscontrol.AccessControl,
	serviceWrapper publicdashboards.ServiceWrapper,
	dashboardService dashboards.DashboardService,
	license licensing.Licensing,
) *PublicDashboardServiceImpl {
	return &PublicDashboardServiceImpl{
		log:                log.New(LogPrefix),
		cfg:                cfg,
		features:           features,
		store:              store,
		intervalCalculator: intervalv2.NewCalculator(),
		QueryDataService:   qds,
		AnnotationsRepo:    anno,
		ac:                 ac,
		serviceWrapper:     serviceWrapper,
		dashboardService:   dashboardService,
		license:            license,
	}
}

func (pd *PublicDashboardServiceImpl) GetPublicDashboardForView(ctx context.Context, accessToken string) (*dtos.DashboardFullWithMeta, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.GetPublicDashboardForView")
	defer span.End()

	pubdash, dash, err := pd.FindEnabledPublicDashboardAndDashboardByAccessToken(ctx, accessToken)
	if err != nil {
		return nil, err
	}

	metrics.MFolderIDsServiceCount.WithLabelValues(metrics.PublicDashboards).Inc()
	meta := dtos.DashboardMeta{
		Slug:                   dash.Slug,
		Type:                   dashboards.DashTypeDB,
		CanStar:                false,
		CanSave:                false,
		CanEdit:                false,
		CanAdmin:               false,
		CanDelete:              false,
		Created:                dash.Created,
		Updated:                dash.Updated,
		Version:                dash.Version,
		IsFolder:               false,
		FolderId:               dash.FolderID, // nolint:staticcheck
		FolderUid:              dash.FolderUID,
		PublicDashboardEnabled: pubdash.IsEnabled,
	}
	dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)

	sanitizeData(dash.Data)

	return &dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}, nil
}

// FindByDashboardUid this method would be replaced by another implementation for Enterprise version
func (pd *PublicDashboardServiceImpl) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindByDashboardUid")
	defer span.End()
	return pd.serviceWrapper.FindByDashboardUid(ctx, orgId, dashboardUid)
}

func (pd *PublicDashboardServiceImpl) Find(ctx context.Context, uid string) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.Find")
	defer span.End()
	pubdash, err := pd.store.Find(ctx, uid)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Find: failed to find public dashboard%w", err)
	}
	return pubdash, nil
}

// FindDashboard Gets a dashboard by Uid
func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindDashboard")
	defer span.End()

	// We don't have a signed in user for public dashboards. We are using Grafana's Identity to query the dashboard.
	dash, err := identity.WithServiceIdentityFn(ctx, orgId, func(ctx context.Context) (*dashboards.Dashboard, error) {
		return pd.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId})
	})
	if err != nil {
		var dashboardErr dashboardaccess.DashboardErr
		if ok := errors.As(err, &dashboardErr); ok {
			if dashboardErr.StatusCode == 404 {
				return nil, ErrDashboardNotFound.Errorf("FindDashboard: dashboard not found by orgId: %d and dashboardUid: %s", orgId, dashboardUid)
			}
		}
		return nil, ErrInternalServerError.Errorf("FindDashboard: failed to find dashboard by orgId: %d and dashboardUid: %s: %w", orgId, dashboardUid, err)
	}

	return dash, nil
}

// FindByAccessToken Gets public dashboard by access token
func (pd *PublicDashboardServiceImpl) FindByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindByAccessToken")
	defer span.End()
	pubdash, err := pd.store.FindByAccessToken(ctx, accessToken)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("FindByAccessToken: failed to find a public dashboard: %w", err)
	}

	if pubdash == nil {
		return nil, ErrPublicDashboardNotFound.Errorf("FindByAccessToken: Public dashboard not found accessToken: %s", accessToken)
	}

	return pubdash, nil
}

// FindEnabledPublicDashboardAndDashboardByAccessToken Gets public dashboard and a dashboard by access token if public dashboard is enabled
func (pd *PublicDashboardServiceImpl) FindEnabledPublicDashboardAndDashboardByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, *dashboards.Dashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindEnabledPublicDashboardAndDashboardByAccessToken")
	defer span.End()
	pubdash, dash, err := pd.FindPublicDashboardAndDashboardByAccessToken(ctx, accessToken)
	if err != nil {
		return pubdash, dash, err
	}

	if !pubdash.IsEnabled {
		return nil, nil, ErrPublicDashboardNotEnabled.Errorf("FindEnabledPublicDashboardAndDashboardByAccessToken: Public dashboard is not enabled accessToken: %s", accessToken)
	}

	if !pd.license.FeatureEnabled(FeaturePublicDashboardsEmailSharing) && pubdash.Share == EmailShareType {
		return nil, nil, ErrPublicDashboardNotFound.Errorf("FindEnabledPublicDashboardAndDashboardByAccessToken: Dashboard not found accessToken: %s", accessToken)
	}

	return pubdash, dash, err
}

// FindPublicDashboardAndDashboardByAccessToken Gets public dashboard and a dashboard by access token
func (pd *PublicDashboardServiceImpl) FindPublicDashboardAndDashboardByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, *dashboards.Dashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindPublicDashboardAndDashboardByAccessToken")
	defer span.End()
	pubdash, err := pd.FindByAccessToken(ctx, accessToken)
	if err != nil {
		return nil, nil, err
	}

	dash, err := pd.FindDashboard(ctx, pubdash.OrgId, pubdash.DashboardUid)
	if err != nil {
		return nil, nil, err
	}

	if dash == nil {
		return nil, nil, ErrPublicDashboardNotFound.Errorf("FindPublicDashboardAndDashboardByAccessToken: Dashboard not found accessToken: %s", accessToken)
	}

	return pubdash, dash, nil
}

// Creates and validates the public dashboard and saves it to the database
func (pd *PublicDashboardServiceImpl) Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.Create")
	defer span.End()
	// validate fields
	err := validation.ValidatePublicDashboard(dto)
	if err != nil {
		return nil, err
	}

	// ensure dashboard exists
	_, err = pd.FindDashboard(ctx, u.OrgID, dto.DashboardUid)
	if err != nil {
		return nil, err
	}

	// validate the dashboard does not already have a public dashboard
	existingPubdash, err := pd.FindByDashboardUid(ctx, u.OrgID, dto.DashboardUid)
	if err != nil && !errors.Is(err, ErrPublicDashboardNotFound) {
		return nil, err
	}

	if existingPubdash != nil {
		// If there is no license and the public dashboard was email-shared, we should update it to public
		if !pd.license.FeatureEnabled(FeaturePublicDashboardsEmailSharing) && existingPubdash.Share == EmailShareType {
			dto.Uid = existingPubdash.Uid
			dto.PublicDashboard.Share = PublicShareType
			return pd.Update(ctx, u, dto)
		}
		return nil, ErrDashboardIsPublic.Errorf("Create: public dashboard for dashboard %s already exists", dto.DashboardUid)
	}

	publicDashboard, err := pd.newCreatePublicDashboard(ctx, dto)
	if err != nil {
		return nil, err
	}

	cmd := SavePublicDashboardCommand{
		PublicDashboard: *publicDashboard,
	}

	affectedRows, err := pd.store.Create(ctx, cmd)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Create: failed to create the public dashboard with Uid %s: %w", publicDashboard.Uid, err)
	} else if affectedRows == 0 {
		return nil, ErrInternalServerError.Errorf("Create: failed to create a database entry for public dashboard with Uid %s. 0 rows changed, no error reported.", publicDashboard.Uid)
	}

	//Get latest public dashboard to return
	newPubdash, err := pd.store.Find(ctx, publicDashboard.Uid)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Create: failed to find the public dashboard: %w", err)
	}

	pd.logIsEnabledChanged(existingPubdash, newPubdash, u)

	return newPubdash, err
}

// Update: updates an existing public dashboard based on publicdashboard.Uid
func (pd *PublicDashboardServiceImpl) Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.Update")
	defer span.End()
	// validate fields
	err := validation.ValidatePublicDashboard(dto)
	if err != nil {
		return nil, err
	}

	// validate dashboard exists
	_, err = pd.FindDashboard(ctx, u.OrgID, dto.DashboardUid)
	if err != nil {
		return nil, err
	}

	// get existing public dashboard if exists
	existingPubdash, err := pd.store.Find(ctx, dto.Uid)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Update: failed to find public dashboard by uid: %s: %w", dto.Uid, err)
	} else if existingPubdash == nil {
		return nil, ErrPublicDashboardNotFound.Errorf("Update: public dashboard not found by uid: %s", dto.Uid)
	}

	// validate the public dashboard belongs to the dashboard
	if existingPubdash.DashboardUid != dto.DashboardUid {
		return nil, ErrInvalidUid.Errorf("Update: the public dashboard does not belong to the dashboard")
	}

	publicDashboard := newUpdatePublicDashboard(dto, existingPubdash)

	// set values to update
	cmd := SavePublicDashboardCommand{
		PublicDashboard: *publicDashboard,
	}

	// persist
	affectedRows, err := pd.store.Update(ctx, cmd)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Update: failed to update public dashboard: %w", err)
	}

	// 404 if not found
	if affectedRows == 0 {
		return nil, ErrPublicDashboardNotFound.Errorf("Update: failed to update public dashboard not found by uid: %s", dto.Uid)
	}

	// get latest public dashboard to return
	newPubdash, err := pd.store.Find(ctx, existingPubdash.Uid)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("Update: failed to find public dashboard by uid: %s: %w", existingPubdash.Uid, err)
	}

	pd.logIsEnabledChanged(existingPubdash, newPubdash, u)

	return newPubdash, nil
}

// NewPublicDashboardUid Generates a unique uid to create a public dashboard. Will make 3 attempts and fail if it cannot find an unused uid
func (pd *PublicDashboardServiceImpl) NewPublicDashboardUid(ctx context.Context) (string, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.NewPublicDashboardUid")
	defer span.End()
	var uid string
	for i := 0; i < 3; i++ {
		uid = util.GenerateShortUID()

		pubdash, _ := pd.store.Find(ctx, uid)
		if pubdash == nil {
			return uid, nil
		}
	}
	return "", ErrInternalServerError.Errorf("failed to generate a unique uid for public dashboard")
}

// NewPublicDashboardAccessToken Generates a unique accessToken to create a public dashboard. Will make 3 attempts and fail if it cannot find an unused access token
func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context.Context) (string, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.NewPublicDashboardAccessToken")
	defer span.End()
	var accessToken string
	for i := 0; i < 3; i++ {
		var err error
		accessToken, err = GenerateAccessToken()
		if err != nil {
			continue
		}

		pubdash, _ := pd.store.FindByAccessToken(ctx, accessToken)
		if pubdash == nil {
			return accessToken, nil
		}
	}
	return "", ErrInternalServerError.Errorf("failed to generate a unique accessToken for public dashboard")
}

// FindAllWithPagination Returns a list of public dashboards by orgId, based on permissions and with pagination
func (pd *PublicDashboardServiceImpl) FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.FindAllWithPagination")
	defer span.End()
	query.Offset = query.Limit * (query.Page - 1)
	resp, err := pd.store.FindAll(ctx, query)
	if err != nil {
		return nil, ErrInternalServerError.Errorf("FindAllWithPagination: GetPublicDashboards: %w", err)
	}

	// join in the dashboard data
	dashUIDs := make([]string, len(resp.PublicDashboards))
	for i, pubdash := range resp.PublicDashboards {
		dashUIDs[i] = pubdash.DashboardUid
	}

	dashboardsFound, err := pd.dashboardService.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
		OrgId:         query.OrgID,
		DashboardUIDs: dashUIDs,
		SignedInUser:  query.User,
		Limit:         int64(len(dashUIDs)),
		Type:          searchstore.TypeDashboard,
	})
	if err != nil {
		return nil, ErrInternalServerError.Errorf("FindAllWithPagination: GetDashboards: %w", err)
	}

	dashMap := make(map[string]dashboards.DashboardSearchProjection)
	for _, dash := range dashboardsFound {
		dashMap[dash.UID] = dash
	}

	// add dashboard title & slug to response, and
	// remove any public dashboards that don't have a corresponding active dashboard that the user has access to
	idx := 0
	for _, pubdash := range resp.PublicDashboards {
		if dash, exists := dashMap[pubdash.DashboardUid]; exists {
			pubdash.Title = dash.Title
			pubdash.Slug = dash.Slug
			resp.PublicDashboards[idx] = pubdash
			idx++
		} else {
			resp.TotalCount--
		}
	}
	resp.PublicDashboards = resp.PublicDashboards[:idx]

	//  sort by title
	sort.Slice(resp.PublicDashboards, func(i, j int) bool {
		return resp.PublicDashboards[i].Title < resp.PublicDashboards[j].Title
	})

	// and now paginate
	start := query.Offset
	end := start + query.Limit
	if start > len(resp.PublicDashboards) {
		start = len(resp.PublicDashboards)
	}
	if end > len(resp.PublicDashboards) {
		end = len(resp.PublicDashboards)
	}
	resp.PublicDashboards = resp.PublicDashboards[start:end]

	resp.Page = query.Page
	resp.PerPage = query.Limit

	return resp, nil
}

func (pd *PublicDashboardServiceImpl) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.ExistsEnabledByDashboardUid")
	defer span.End()
	return pd.store.ExistsEnabledByDashboardUid(ctx, dashboardUid)
}

func (pd *PublicDashboardServiceImpl) ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.ExistsEnabledByAccessToken")
	defer span.End()
	return pd.store.ExistsEnabledByAccessToken(ctx, accessToken)
}

func (pd *PublicDashboardServiceImpl) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.GetOrgIdByAccessToken")
	defer span.End()
	return pd.store.GetOrgIdByAccessToken(ctx, accessToken)
}

func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, uid string, dashboardUid string) error {
	ctx, span := tracer.Start(ctx, "publicdashboards.Delete")
	defer span.End()
	// get existing public dashboard if exists
	existingPubdash, err := pd.store.Find(ctx, uid)
	if err != nil {
		return ErrInternalServerError.Errorf("Delete: failed to find public dashboard by uid: %s: %w", uid, err)
	}
	if existingPubdash == nil {
		return ErrPublicDashboardNotFound.Errorf("Delete: public dashboard not found by uid: %s", uid)
	}

	// validate the public dashboard belongs to the dashboard
	if existingPubdash.DashboardUid != dashboardUid {
		return ErrInvalidUid.Errorf("Delete: the public dashboard does not belong to the dashboard")
	}
	return pd.serviceWrapper.Delete(ctx, uid)
}

// intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards
// we are doing the same for public dashboards but because this access would be public, we need a way to keep this
// values inside reasonable bounds to avoid an attack that could hit data sources with a small interval and a big
// time range and perform big calculations
// this is an additional validation, all data sources implements QueryData interface and should have proper validations
// of these limits
// for the maxDataPoints we took a hard limit from prometheus which is 11000
func (pd *PublicDashboardServiceImpl) getSafeIntervalAndMaxDataPoints(reqDTO PublicDashboardQueryDTO, ts TimeSettings) (int64, int64) {
	// arbitrary max value for all data sources, it is actually a hard limit defined in prometheus
	safeResolution := int64(11000)

	// interval calculated on the frontend
	interval := time.Duration(reqDTO.IntervalMs) * time.Millisecond

	// calculate a safe interval with time range from dashboard and safeResolution
	dataTimeRange := gtime.NewTimeRange(ts.From, ts.To)
	tr := backend.TimeRange{
		From: dataTimeRange.GetFromAsTimeUTC(),
		To:   dataTimeRange.GetToAsTimeUTC(),
	}
	safeInterval := pd.intervalCalculator.CalculateSafeInterval(tr, safeResolution)

	if interval > safeInterval.Value {
		return reqDTO.IntervalMs, reqDTO.MaxDataPoints
	}

	return safeInterval.Value.Milliseconds(), safeResolution
}

// Log when PublicDashboard.ExistsEnabledByDashboardUid changed
func (pd *PublicDashboardServiceImpl) logIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard, u *user.SignedInUser) {
	if publicDashboardIsEnabledChanged(existingPubdash, newPubdash) {
		verb := "disabled"
		if newPubdash.IsEnabled {
			verb = "enabled"
		}
		pd.log.Info("Public dashboard "+verb, "publicDashboardUid", newPubdash.Uid, "dashboardUid", newPubdash.DashboardUid, "user", u.Login)
	}
}

func (pd *PublicDashboardServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) {
	return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards")
}

// Checks to see if PublicDashboard.ExistsEnabledByDashboardUid is true on create or changed on update
func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard) bool {
	// creating dashboard, enabled true
	newDashCreated := existingPubdash == nil && newPubdash.IsEnabled
	// updating dashboard, enabled changed
	isEnabledChanged := existingPubdash != nil && newPubdash.IsEnabled != existingPubdash.IsEnabled
	return newDashCreated || isEnabledChanged
}

// GenerateAccessToken generates an uuid formatted without dashes to use as access token
func GenerateAccessToken() (string, error) {
	token, err := uuid.NewRandom()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", token[:]), nil
}

func (pd *PublicDashboardServiceImpl) newCreatePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (*PublicDashboard, error) {
	ctx, span := tracer.Start(ctx, "publicdashboards.newCreatePublicDashboard")
	defer span.End()
	//Check if uid already exists, if none then auto generate
	var err error
	uid := dto.PublicDashboard.Uid

	if uid != "" {
		existingPubdash, _ := pd.store.Find(ctx, uid)
		if existingPubdash != nil {
			return nil, ErrPublicDashboardUidExists.Errorf("Create: public dashboard uid %s already exists", uid)
		}
	} else {
		uid, err = pd.NewPublicDashboardUid(ctx)
		if err != nil {
			return nil, err
		}
	}

	//Check if accessToken already exists, if none then auto generate
	accessToken := dto.PublicDashboard.AccessToken
	if accessToken != "" {
		existingPubdash, _ := pd.store.FindByAccessToken(ctx, accessToken)
		if existingPubdash != nil {
			return nil, ErrPublicDashboardAccessTokenExists.Errorf("Create: public dashboard access token %s already exists", accessToken)
		}
	} else {
		accessToken, err = pd.NewPublicDashboardAccessToken(ctx)
		if err != nil {
			return nil, err
		}
	}

	isEnabled := returnValueOrDefault(dto.PublicDashboard.IsEnabled, false)
	annotationsEnabled := returnValueOrDefault(dto.PublicDashboard.AnnotationsEnabled, false)
	timeSelectionEnabled := returnValueOrDefault(dto.PublicDashboard.TimeSelectionEnabled, false)

	share := dto.PublicDashboard.Share
	if dto.PublicDashboard.Share == "" {
		share = PublicShareType
	}

	now := time.Now()

	return &PublicDashboard{
		Uid:                  uid,
		DashboardUid:         dto.DashboardUid,
		OrgId:                dto.OrgID,
		IsEnabled:            isEnabled,
		AnnotationsEnabled:   annotationsEnabled,
		TimeSelectionEnabled: timeSelectionEnabled,
		TimeSettings:         &TimeSettings{},
		Share:                share,
		CreatedBy:            dto.UserId,
		CreatedAt:            now,
		UpdatedBy:            dto.UserId,
		UpdatedAt:            now,
		AccessToken:          accessToken,
	}, nil
}

func newUpdatePublicDashboard(dto *SavePublicDashboardDTO, pd *PublicDashboard) *PublicDashboard {
	pubdashDTO := dto.PublicDashboard
	timeSelectionEnabled := returnValueOrDefault(pubdashDTO.TimeSelectionEnabled, pd.TimeSelectionEnabled)
	isEnabled := returnValueOrDefault(pubdashDTO.IsEnabled, pd.IsEnabled)
	annotationsEnabled := returnValueOrDefault(pubdashDTO.AnnotationsEnabled, pd.AnnotationsEnabled)

	share := pubdashDTO.Share
	if pubdashDTO.Share == "" {
		share = pd.Share
	}

	return &PublicDashboard{
		Uid:                  pd.Uid,
		IsEnabled:            isEnabled,
		AnnotationsEnabled:   annotationsEnabled,
		TimeSelectionEnabled: timeSelectionEnabled,
		TimeSettings:         pd.TimeSettings,
		Share:                share,
		UpdatedBy:            dto.UserId,
		UpdatedAt:            time.Now(),
	}
}

func returnValueOrDefault(value *bool, defaultValue bool) bool {
	if value != nil {
		return *value
	}

	return defaultValue
}
