package collections

import (
	"time"

	"git.perx.ru/perxis/perxis-go/pkg/data"
	"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
	ReadFilter      string              // Дополнительный фильтр на чтение
	WriteFilter     string              // Дополнительный фильтр на запись
	Hidden          bool                // Скрыть коллекцию в интерфейсе от пользователя
}

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)),
		Hidden:          a.Hidden,
	}

	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"`           // Коллекция скрыта в административном интерфейсе

	NoArchive bool `json:"no_archive" bson:"no_archive,omitempty"` // Коллекция без архива

	NoRevisions  bool          `json:"no_revisions" bson:"no_revisions,omitempty"`   // Не хранить историю изменений
	MaxRevisions uint32        `json:"max_revisions" bson:"max_revisions,omitempty"` // Максимальное количество хранимых ревизий
	RevisionTTL  time.Duration `json:"revision_ttl" bson:"revision_ttl,omitempty"`   // Время жизни ревизии

	// Все записи коллекции считаются опубликованными, функции публикации и снятия с публикации недоступны.
	// При включении параметра коллекции "без публикации" все записи, независимо от статуса, будут считаться опубликованными.
	// При отключении параметра "без публикации" статусы публикации будут восстановлены.
	NoPublish bool `json:"no_publish" bson:"no_publish,omitempty"`

	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:"-"`
}

// GetID возвращает идентификатор коллекции
func (c Collection) GetID() string {
	return c.ID
}

func (c Collection) GetSpaceID() string {
	return c.SpaceID
}

// Equal сравнивает две коллекции, за исключением Schema, Access, StateInfo и Config
func (c Collection) Equal(other *Collection) bool {
	if c.ID != other.ID ||
		c.SpaceID != other.SpaceID ||
		c.EnvID != other.EnvID ||
		c.Name != other.Name ||
		c.IsNoData() != other.IsNoData() ||
		c.IsSingle() != other.IsSingle() ||
		c.IsSystem() != other.IsSystem() ||
		c.Hidden != other.Hidden ||
		c.NoPublish != other.NoPublish ||
		c.NoArchive != other.NoArchive ||
		c.NoRevisions != other.NoRevisions ||
		c.MaxRevisions != other.MaxRevisions ||
		c.RevisionTTL != other.RevisionTTL ||
		!c.View.Equal(other.View) ||
		!data.ElementsMatch(c.Tags, other.Tags) {
		return false
	}
	return true
}

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,
		NoPublish:    c.NoPublish,
		NoArchive:    c.NoArchive,
		NoRevisions:  c.NoRevisions,
		MaxRevisions: c.MaxRevisions,
		RevisionTTL:  c.RevisionTTL,
	}

	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
}

func AccessFromRule(rule *permission.Rule) *Access {
	if rule == nil {
		return nil
	}

	return &Access{
		Actions:         rule.Actions,
		HiddenFields:    rule.HiddenFields,
		DenyReadFields:  rule.DenyReadFields,
		DenyWriteFields: rule.DenyWriteFields,
		ReadFilter:      rule.ReadFilter,
		WriteFilter:     rule.WriteFilter,
		Hidden:          rule.Hidden,
	}
}

func RuleFromAccess(access *Access) *permission.Rule {
	if access == nil {
		return nil
	}

	return &permission.Rule{
		Actions:         access.Actions,
		HiddenFields:    access.HiddenFields,
		DenyReadFields:  access.DenyReadFields,
		DenyWriteFields: access.DenyWriteFields,
		ReadFilter:      access.ReadFilter,
		WriteFilter:     access.WriteFilter,
		Hidden:          access.Hidden,
	}
}