package probeservices

import (
	"encoding/json"
	"time"

	"github.com/ooni/probe-cli/v3/internal/engine/model"
)

// State is the state stored inside the state file
type State struct {
	ClientID string
	Expire   time.Time
	Password string
	Token    string
}

// Auth returns an authentication structure, if possible, otherwise
// it returns nil, meaning that you should login again.
func (s State) Auth() *LoginAuth {
	if s.Token == "" {
		return nil
	}
	if time.Now().Add(30 * time.Second).After(s.Expire) {
		return nil
	}
	return &LoginAuth{Expire: s.Expire, Token: s.Token}
}

// Credentials returns login credentials, if possible, otherwise it
// returns nil, meaning that you should create an account.
func (s State) Credentials() *LoginCredentials {
	if s.ClientID == "" {
		return nil
	}
	if s.Password == "" {
		return nil
	}
	return &LoginCredentials{ClientID: s.ClientID, Password: s.Password}
}

// StateFile is the orchestra state file. It is backed by
// a generic key-value store configured by the user.
type StateFile struct {
	Store model.KeyValueStore
	key   string
}

// NewStateFile creates a new state file backed by a key-value store
func NewStateFile(kvstore model.KeyValueStore) StateFile {
	return StateFile{key: "orchestra.state", Store: kvstore}
}

// SetMockable is a mockable version of Set
func (sf StateFile) SetMockable(s State, mf func(interface{}) ([]byte, error)) error {
	data, err := mf(s)
	if err != nil {
		return err
	}
	return sf.Store.Set(sf.key, data)
}

// Set saves the current state on the key-value store.
func (sf StateFile) Set(s State) error {
	return sf.SetMockable(s, json.Marshal)
}

// GetMockable is a mockable version of Get
func (sf StateFile) GetMockable(sfget func(string) ([]byte, error),
	unmarshal func([]byte, interface{}) error) (State, error) {
	value, err := sfget(sf.key)
	if err != nil {
		return State{}, err
	}
	var state State
	if err := unmarshal(value, &state); err != nil {
		return State{}, err
	}
	return state, nil
}

// Get returns the current state. In case of any error with the
// underlying key-value store, we return an empty state.
func (sf StateFile) Get() (state State) {
	state, _ = sf.GetMockable(sf.Store.Get, json.Unmarshal)
	return
}