package v2migrator

import (
	"compress/gzip"
	"encoding/json"
	"os"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/Luzifer/twitch-bot/v3/internal/v2migrator/crypt"
	"github.com/Luzifer/twitch-bot/v3/pkg/database"
	"github.com/Luzifer/twitch-bot/v3/plugins"
)

type (
	Migrator interface {
		Load(filename, encryptionPass string) error
		Migrate(db database.Connector) error
	}

	storageExtendedPermission struct {
		AccessToken  string   `encrypt:"true" json:"access_token,omitempty"`
		RefreshToken string   `encrypt:"true" json:"refresh_token,omitempty"`
		Scopes       []string `json:"scopes,omitempty"`
	}

	storageFile struct {
		Counters  map[string]int64              `json:"counters"`
		Timers    map[string]plugins.TimerEntry `json:"timers"`
		Variables map[string]string             `json:"variables"`

		ModuleStorage struct {
			ModOverlays storageModOverlays `json:"f9ca2b3a-baf6-45ea-a347-c626168665e8"`
			ModQuoteDB  storageModQuoteDB  `json:"917c83ee-ed40-41e4-a558-1c2e59fdf1f5"`
		} `json:"module_storage"`

		ExtendedPermissions map[string]*storageExtendedPermission `json:"extended_permissions"`

		EventSubSecret string `encrypt:"true" json:"event_sub_secret,omitempty"`

		BotAccessToken  string `encrypt:"true" json:"bot_access_token,omitempty"`
		BotRefreshToken string `encrypt:"true" json:"bot_refresh_token,omitempty"`
	}
)

func NewStorageFile() Migrator {
	return &storageFile{
		Counters:  map[string]int64{},
		Timers:    map[string]plugins.TimerEntry{},
		Variables: map[string]string{},

		ExtendedPermissions: map[string]*storageExtendedPermission{},
	}
}

func (s *storageFile) Load(filename, encryptionPass string) error {
	f, err := os.Open(filename)
	if err != nil {
		if os.IsNotExist(err) {
			// Store init state
			return nil
		}
		return errors.Wrap(err, "open storage file")
	}
	defer f.Close()

	zf, err := gzip.NewReader(f)
	if err != nil {
		return errors.Wrap(err, "create gzip reader")
	}
	defer zf.Close()

	if err = json.NewDecoder(zf).Decode(s); err != nil {
		return errors.Wrap(err, "decode storage object")
	}

	if err = crypt.DecryptFields(s, encryptionPass); err != nil {
		return errors.Wrap(err, "decrypting storage object")
	}

	return nil
}

func (s storageFile) Migrate(db database.Connector) error {
	var bat string
	err := db.ReadCoreMeta("bot_access_token", &bat)
	switch {
	case err == nil:
		return errors.New("Access token is set, database already initialized")

	case errors.Is(err, database.ErrCoreMetaNotFound):
		// This is the expected state

	default:
		return errors.Wrap(err, "checking for bot access token")
	}

	for name, fn := range map[string]func(database.Connector) error{
		// Core
		"core":        s.migrateCoreKV,
		"counter":     s.migrateCounters,
		"permissions": s.migratePermissions,
		"timers":      s.migrateTimers,
		"variables":   s.migrateVariables,
		// Modules
		"mod_overlays": s.ModuleStorage.ModOverlays.migrate,
		"mod_quotedb":  s.ModuleStorage.ModQuoteDB.migrate,
	} {
		logrus.WithField("module", name).Info("Starting migration...")
		if err = fn(db); err != nil {
			return errors.Wrapf(err, "executing %q migration", name)
		}
	}

	return nil
}