package prefimpl

import (
	"context"
	"errors"
	"time"

	"github.com/grafana/grafana/pkg/infra/db"
	pref "github.com/grafana/grafana/pkg/services/preference"
	"github.com/grafana/grafana/pkg/setting"
)

type Service struct {
	store    store
	defaults pref.Preference
}

func ProvideService(db db.DB, cfg *setting.Cfg) pref.Service {
	return &Service{
		store: &sqlStore{
			db: db,
		},
		defaults: prefsFromConfig(cfg),
	}
}

func prefsFromConfig(cfg *setting.Cfg) pref.Preference {
	return pref.Preference{
		Theme:           cfg.DefaultTheme,
		Timezone:        cfg.DateFormats.DefaultTimezone,
		WeekStart:       &cfg.DateFormats.DefaultWeekStart,
		HomeDashboardID: 0,
		JSONData: &pref.PreferenceJSONData{
			Language: cfg.DefaultLanguage,
		},
	}
}

func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreferenceWithDefaultsQuery) (*pref.Preference, error) {
	listQuery := &pref.Preference{
		Teams:  query.Teams,
		OrgID:  query.OrgID,
		UserID: query.UserID,
	}

	prefs, err := s.store.List(ctx, listQuery)
	if err != nil {
		return nil, err
	}

	res := s.GetDefaults()
	for _, p := range prefs {
		if p.Theme != "" {
			res.Theme = p.Theme
		}
		if p.Timezone != "" {
			res.Timezone = p.Timezone
		}
		if p.WeekStart != nil && *p.WeekStart != "" {
			res.WeekStart = p.WeekStart
		}
		if p.HomeDashboardID != 0 {
			res.HomeDashboardID = p.HomeDashboardID
		}
		if p.JSONData != nil {
			if p.JSONData.Language != "" {
				res.JSONData.Language = p.JSONData.Language
			}

			if p.JSONData.QueryHistory.HomeTab != "" {
				res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab
			}

			if p.JSONData.Navbar.BookmarkUrls != nil {
				res.JSONData.Navbar.BookmarkUrls = p.JSONData.Navbar.BookmarkUrls
			}

			if p.JSONData.CookiePreferences != nil {
				res.JSONData.CookiePreferences = p.JSONData.CookiePreferences
			}
		}
	}

	return res, err
}

func (s *Service) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pref.Preference, error) {
	getPref := &pref.Preference{
		OrgID:  query.OrgID,
		UserID: query.UserID,
		TeamID: query.TeamID,
	}
	prefs, err := s.store.Get(ctx, getPref)
	if errors.Is(err, pref.ErrPrefNotFound) {
		return &pref.Preference{}, nil
	}
	if err != nil {
		return nil, err
	}
	return prefs, nil
}

func (s *Service) Save(ctx context.Context, cmd *pref.SavePreferenceCommand) error {
	jsonData, err := preferenceData(cmd)
	if err != nil {
		return err
	}

	preference, err := s.store.Get(ctx, &pref.Preference{
		OrgID:  cmd.OrgID,
		UserID: cmd.UserID,
		TeamID: cmd.TeamID,
	})
	if err != nil {
		if errors.Is(err, pref.ErrPrefNotFound) {
			preference := &pref.Preference{
				UserID:          cmd.UserID,
				OrgID:           cmd.OrgID,
				TeamID:          cmd.TeamID,
				HomeDashboardID: cmd.HomeDashboardID,
				Timezone:        cmd.Timezone,
				WeekStart:       &cmd.WeekStart,
				Theme:           cmd.Theme,
				Created:         time.Now(),
				Updated:         time.Now(),
				JSONData:        jsonData,
			}
			_, err = s.store.Insert(ctx, preference)
			if err != nil {
				return err
			}
		}
		return err
	}

	preference.Timezone = cmd.Timezone
	preference.WeekStart = &cmd.WeekStart
	preference.Theme = cmd.Theme
	preference.Updated = time.Now()
	preference.Version += 1
	preference.HomeDashboardID = cmd.HomeDashboardID
	preference.JSONData = jsonData

	return s.store.Update(ctx, preference)
}

func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) error {
	var exists bool
	preference, err := s.store.Get(ctx, &pref.Preference{
		OrgID:  cmd.OrgID,
		UserID: cmd.UserID,
		TeamID: cmd.TeamID,
	})
	if err != nil && !errors.Is(err, pref.ErrPrefNotFound) {
		return err
	}

	if errors.Is(err, pref.ErrPrefNotFound) {
		preference = &pref.Preference{
			UserID:   cmd.UserID,
			OrgID:    cmd.OrgID,
			TeamID:   cmd.TeamID,
			Created:  time.Now(),
			JSONData: &pref.PreferenceJSONData{},
		}
	} else {
		exists = true
	}

	if cmd.Language != nil {
		if preference.JSONData == nil {
			preference.JSONData = &pref.PreferenceJSONData{}
		}
		preference.JSONData.Language = *cmd.Language
	}

	if cmd.Navbar != nil && cmd.Navbar.BookmarkUrls != nil {
		if preference.JSONData == nil {
			preference.JSONData = &pref.PreferenceJSONData{}
		}
		preference.JSONData.Navbar.BookmarkUrls = cmd.Navbar.BookmarkUrls
	}

	if cmd.QueryHistory != nil {
		if preference.JSONData == nil {
			preference.JSONData = &pref.PreferenceJSONData{}
		}
		if cmd.QueryHistory.HomeTab != "" {
			preference.JSONData.QueryHistory.HomeTab = cmd.QueryHistory.HomeTab
		}
	}

	if cmd.HomeDashboardID != nil {
		preference.HomeDashboardID = *cmd.HomeDashboardID
	}

	if cmd.CookiePreferences != nil {
		cookies, err := parseCookiePreferences(cmd.CookiePreferences)
		if err != nil {
			return err
		}

		if preference.JSONData == nil {
			preference.JSONData = &pref.PreferenceJSONData{}
		}
		preference.JSONData.CookiePreferences = cookies
	}

	if cmd.Timezone != nil {
		preference.Timezone = *cmd.Timezone
	}

	if cmd.WeekStart != nil {
		preference.WeekStart = cmd.WeekStart
	}

	if cmd.Theme != nil {
		preference.Theme = *cmd.Theme
	}

	preference.Updated = time.Now()
	preference.Version += 1

	if exists {
		err = s.store.Update(ctx, preference)
	} else {
		_, err = s.store.Insert(ctx, preference)
	}
	return err
}

func (s *Service) GetDefaults() *pref.Preference {
	return &pref.Preference{
		Theme:           s.defaults.Theme,
		Timezone:        s.defaults.Timezone,
		WeekStart:       s.defaults.WeekStart,
		HomeDashboardID: 0,
		JSONData: &pref.PreferenceJSONData{
			Language: s.defaults.JSONData.Language,
		},
	}
}

func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
	return s.store.DeleteByUser(ctx, userID)
}

func parseCookiePreferences(prefs []pref.CookieType) (map[string]struct{}, error) {
	allowed := map[pref.CookieType]struct{}{
		"analytics":   {},
		"performance": {},
		"functional":  {},
	}

	m := map[string]struct{}{}
	for _, c := range prefs {
		if _, ok := allowed[c]; !ok {
			return nil, pref.ErrUnknownCookieType.Errorf("'%s' is not an allowed cookie type", c)
		}

		m[string(c)] = struct{}{}
	}
	return m, nil
}

func preferenceData(cmd *pref.SavePreferenceCommand) (*pref.PreferenceJSONData, error) {
	jsonData := &pref.PreferenceJSONData{
		Language: cmd.Language,
	}

	if cmd.Navbar != nil {
		jsonData.Navbar = *cmd.Navbar
	}
	if cmd.QueryHistory != nil {
		jsonData.QueryHistory = *cmd.QueryHistory
	}
	if cmd.CookiePreferences != nil {
		cookies, err := parseCookiePreferences(cmd.CookiePreferences)
		if err != nil {
			return nil, err
		}
		jsonData.CookiePreferences = cookies
	}

	return jsonData, nil
}
