package collections

import (
	"time"

	"git.perx.ru/perxis/perxis-go/pkg/permission"
	"git.perx.ru/perxis/perxis-go/pkg/schema"
)

// Config
type Config struct {
	SourceSpaceID      string
	SourceEnvID        string
	SourceCollectionID string
	SourceSchema       *schema.Schema
}

// Access - описывает текущие ограничения на доступ к элементам коллекции для текущего
// пользователя
type Access struct {
	Actions         []permission.Action // Список разрешенных действия с элементами коллекции
	HiddenFields    []string            // Поля не отображаемые в интерфейсе и не возвращаемые API
	DenyReadFields  []string            // Поля недоступные для редактирования и не обновляемые через API
	DenyWriteFields []string            // Поля отображаемые в интерфейсе, но не возвращаемые в API
}

func (a Access) Clone() *Access {

	clone := &Access{
		Actions:         make([]permission.Action, len(a.Actions)),
		HiddenFields:    make([]string, len(a.HiddenFields)),
		DenyReadFields:  make([]string, len(a.DenyReadFields)),
		DenyWriteFields: make([]string, len(a.DenyWriteFields)),
	}

	copy(clone.Actions, a.Actions)
	copy(clone.HiddenFields, a.HiddenFields)
	copy(clone.DenyReadFields, a.DenyReadFields)
	copy(clone.DenyWriteFields, a.DenyWriteFields)

	return clone
}

func (a Access) Can(action permission.Action) bool {
	for _, act := range a.Actions {
		if act == action {
			return true
		}
	}
	return false
}

type Collection struct {
	ID      string         `json:"id" bson:"id"`
	SpaceID string         `json:"spaceId" bson:"-"`
	EnvID   string         `json:"envId" bson:"-"`
	Name    string         `json:"name" bson:"name"`
	Single  *bool          `json:"single" bson:"single,omitempty"` // В коллекции может быть только один документ
	System  *bool          `json:"system" bson:"system,omitempty"` // Системная коллекция
	NoData  *bool          `json:"no_data" bson:"no_data"`         // Коллекция не содержит элементы. Схема используется для включения в другие схемы
	Hidden  bool           `json:"hidden" bson:"hidden"`           // Коллекция скрыта в административном интерфейсе
	Schema  *schema.Schema `json:"schema" bson:"schema"`
	Access  *Access        `json:"access" bson:"-"` // Ограничения на доступ к элементам коллекции. Отсутствие объекта означает неограниченный доступ

	// StateInfo отображает состояние коллекции:
	// - State: идентификатор состояния коллекции (new/preparing/ready/error/changed)
	// - Info: дополнительная информация о состоянии коллекции (например, если при
	//   применении схемы к коллекции произошла ошибка)
	// - StartedAt: время, в которое коллекция перешла в состояние `Preparing`
	StateInfo *StateInfo `json:"state_info" bson:"state_info,omitempty"` // todo: показывать в интерфейсе как readonly

	// View - Если значение поля непустое, то коллекция является View ("отображением"
	// части данных другой коллекции согласно View.Filter)
	View *View `json:"view,omitempty" bson:"view,omitempty"`

	// Tags - список тегов коллекции. Добавляются при отправке событий events
	Tags []string `json:"tags,omitempty" bson:"tags,omitempty"`

	Config *Config `json:"-" bson:"-"`
}

type View struct {
	SpaceID      string `json:"space_id" bson:"space_id"`             // SpaceID оригинальной коллекции
	EnvID        string `json:"environment_id" bson:"environment_id"` // EnvID оригинальной коллекции
	CollectionID string `json:"collection_id" bson:"collection_id"`   // CollectionID оригинальной коллекции
	Filter       string `json:"filter" bson:"filter,omitempty"`       // Правила фильтрации записей оригинальной коллекции
}

func (v *View) Equal(v1 *View) bool {
	return v == v1 || v != nil && v1 != nil &&
		v.SpaceID == v1.SpaceID &&
		v.EnvID == v1.EnvID &&
		v.CollectionID == v1.CollectionID &&
		v.Filter == v1.Filter
}

type StateInfo struct {
	State     State     `json:"state" bson:"state"`
	Info      string    `json:"info" bson:"info"`
	StartedAt time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"`
	DBVersion uint32    `json:"db_version" bson:"db_version"`
}

type State int

func (s State) String() string {
	var state string

	switch s {
	case StateNew:
		state = "New"
	case StatePreparing:
		state = "Preparing"
	case StateReady:
		state = "Ready"
	case StateError:
		state = "Error"
	case StateChanged:
		state = "Changed"
	default:
		state = "Unknown"
	}

	return state
}

const (
	StateNew State = iota
	StatePreparing
	StateReady
	StateError
	StateChanged
)

func (c Collection) Clone() *Collection {

	clone := &Collection{
		ID:      c.ID,
		SpaceID: c.SpaceID,
		EnvID:   c.EnvID,
		Name:    c.Name,
		NoData:  c.NoData,
		Hidden:  c.Hidden,
	}

	if c.Single != nil {
		single := *c.Single
		clone.Single = &single
	}
	if c.System != nil {
		system := *c.System
		clone.System = &system
	}
	if c.Schema != nil {
		clone.Schema = c.Schema.Clone(false)
	}
	if c.Access != nil {
		clone.Access = c.Access.Clone()
	}
	if c.StateInfo != nil {
		info := *c.StateInfo
		clone.StateInfo = &info
	}
	if c.View != nil {
		view := *c.View
		clone.View = &view
	}
	if c.Config != nil {
		cfg := *c.Config
		clone.Config = &cfg
	}
	if c.Tags != nil {
		clone.Tags = append([]string{}, c.Tags...)
	}

	return clone
}

func (c Collection) IsSingle() bool {
	return c.Single != nil && *c.Single
}

func (c Collection) IsNoData() bool {
	return c.NoData != nil && *c.NoData
}

func (c Collection) IsSystem() bool {
	return c.System != nil && *c.System
}

func (c Collection) IsView() bool {
	return c.View != nil
}

func GetCollectionsIDs(collections []*Collection) []string {
	res := make([]string, len(collections))
	for i, c := range collections {
		res[i] = c.ID
	}
	return res
}