package supportbundlesimpl

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"

	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/infra/kvstore"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/services/supportbundles"
)

const (
	defaultBundleExpiration = 72 * time.Hour // 72h
)

const key = "count"

func newStore(kv kvstore.KVStore) *store {
	return &store{
		kv:     kvstore.WithNamespace(kv, 0, "supportbundle"),
		statKV: kvstore.WithNamespace(kv, 0, "supportbundlestats"),
		log:    log.New("supportbundle.store"),
	}
}

type store struct {
	kv     *kvstore.NamespacedKVStore
	log    log.Logger
	mu     sync.Mutex
	statKV *kvstore.NamespacedKVStore
}

type bundleStore interface {
	Create(ctx context.Context, usr identity.Requester) (*supportbundles.Bundle, error)
	Get(ctx context.Context, uid string) (*supportbundles.Bundle, error)
	StatsCount(ctx context.Context) (int64, error)
	List() ([]supportbundles.Bundle, error)
	Remove(ctx context.Context, uid string) error
	Update(ctx context.Context, uid string, state supportbundles.State, tarBytes []byte) error
}

func (s *store) Create(ctx context.Context, usr identity.Requester) (*supportbundles.Bundle, error) {
	uid, err := uuid.NewRandom()
	if err != nil {
		return nil, err
	}

	bundle := supportbundles.Bundle{
		UID:       uid.String(),
		State:     supportbundles.StatePending,
		Creator:   usr.GetLogin(),
		CreatedAt: time.Now().Unix(),
		ExpiresAt: time.Now().Add(defaultBundleExpiration).Unix(),
	}

	s.mu.Lock()

	bundlesCreatedString, _, err := s.statKV.Get(ctx, key)
	if err != nil {
		s.log.Warn("An error has occurred upon retrieving value at statKV", "key", key)
	}

	bundlesCreated, err := strconv.ParseInt(bundlesCreatedString, 10, 64)
	if err != nil {
		s.log.Warn("No value was found at statKV", "key", key)
	}

	bundlesCreated = bundlesCreated + 1

	if err := s.statKV.Set(ctx, key, fmt.Sprint(bundlesCreated)); err != nil {
		s.log.Warn("An error has occurred upon setting a value at statKV", "key", key)
	}
	s.mu.Unlock()

	if err := s.set(ctx, &bundle); err != nil {
		return nil, err
	}
	return &bundle, nil
}

func (s *store) Update(ctx context.Context, uid string, state supportbundles.State, tarBytes []byte) error {
	bundle, err := s.Get(ctx, uid)
	if err != nil {
		return err
	}

	bundle.State = state
	bundle.TarBytes = tarBytes

	return s.set(ctx, bundle)
}

func (s *store) set(ctx context.Context, bundle *supportbundles.Bundle) error {
	data, err := json.Marshal(&bundle)
	if err != nil {
		return err
	}
	return s.kv.Set(ctx, bundle.UID, string(data))
}

func (s *store) Get(ctx context.Context, uid string) (*supportbundles.Bundle, error) {
	data, ok, err := s.kv.Get(ctx, uid)
	if err != nil {
		return nil, err
	}
	if !ok {
		// FIXME: handle not found
		return nil, errors.New("not found")
	}
	var b supportbundles.Bundle
	if err := json.NewDecoder(strings.NewReader(data)).Decode(&b); err != nil {
		return nil, err
	}

	return &b, nil
}

func (s *store) Remove(ctx context.Context, uid string) error {
	return s.kv.Del(ctx, uid)
}

func (s *store) List() ([]supportbundles.Bundle, error) {
	data, err := s.kv.GetAll(context.Background())
	if err != nil {
		return nil, err
	}

	res := make([]supportbundles.Bundle, 0)
	for _, items := range data {
		for _, s := range items {
			var b supportbundles.Bundle
			if err := json.NewDecoder(strings.NewReader(s)).Decode(&b); err != nil {
				return nil, err
			}

			b.TarBytes = nil
			res = append(res, b)
		}
	}

	sort.Slice(res, func(i, j int) bool {
		return res[i].CreatedAt > res[j].CreatedAt
	})

	return res, nil
}

func (s *store) StatsCount(ctx context.Context) (int64, error) {
	countString, exists, err := s.statKV.Get(ctx, key)
	if err != nil {
		return 0, err
	}
	if !exists {
		return 0, nil
	}
	return strconv.ParseInt(countString, 10, 64)
}
