package model

import (
	"encoding/json"
	"io"
	"time"
)

type BoardType string
type BoardRole string

const (
	BoardTypeOpen    BoardType = "O"
	BoardTypePrivate BoardType = "P"
)

const (
	BoardRoleNone      BoardRole = ""
	BoardRoleViewer    BoardRole = "viewer"
	BoardRoleCommenter BoardRole = "commenter"
	BoardRoleEditor    BoardRole = "editor"
	BoardRoleAdmin     BoardRole = "admin"
)

// Board groups a set of blocks and its layout
// swagger:model
type Board struct {
	// The ID for the board
	// required: true
	ID string `json:"id"`

	// The ID of the team that the board belongs to
	// required: true
	TeamID string `json:"teamId"`

	// The ID of the channel that the board was created from
	// required: false
	ChannelID string `json:"channelId"`

	// The ID of the user that created the board
	// required: true
	CreatedBy string `json:"createdBy"`

	// The ID of the last user that updated the board
	// required: true
	ModifiedBy string `json:"modifiedBy"`

	// The type of the board
	// required: true
	Type BoardType `json:"type"`

	// The minimum role applied when somebody joins the board
	// required: true
	MinimumRole BoardRole `json:"minimumRole"`

	// The title of the board
	// required: false
	Title string `json:"title"`

	// The description of the board
	// required: false
	Description string `json:"description"`

	// The icon of the board
	// required: false
	Icon string `json:"icon"`

	// Indicates if the board shows the description on the interface
	// required: false
	ShowDescription bool `json:"showDescription"`

	// Marks the template boards
	// required: false
	IsTemplate bool `json:"isTemplate"`

	// Marks the template boards
	// required: false
	TemplateVersion int `json:"templateVersion"`

	// The properties of the board
	// required: false
	Properties map[string]interface{} `json:"properties"`

	// The properties of the board cards
	// required: false
	CardProperties []map[string]interface{} `json:"cardProperties"`

	// The creation time in miliseconds since the current epoch
	// required: true
	CreateAt int64 `json:"createAt"`

	// The last modified time in miliseconds since the current epoch
	// required: true
	UpdateAt int64 `json:"updateAt"`

	// The deleted time in miliseconds since the current epoch. Set to indicate this block is deleted
	// required: false
	DeleteAt int64 `json:"deleteAt"`
}

// BoardPatch is a patch for modify boards
// swagger:model
type BoardPatch struct {
	// The type of the board
	// required: false
	Type *BoardType `json:"type"`

	// The minimum role applied when somebody joins the board
	// required: false
	MinimumRole *BoardRole `json:"minimumRole"`

	// The title of the board
	// required: false
	Title *string `json:"title"`

	// The description of the board
	// required: false
	Description *string `json:"description"`

	// The icon of the board
	// required: false
	Icon *string `json:"icon"`

	// Indicates if the board shows the description on the interface
	// required: false
	ShowDescription *bool `json:"showDescription"`

	// Indicates if the board shows the description on the interface
	// required: false
	ChannelID *string `json:"channelId"`

	// The board updated properties
	// required: false
	UpdatedProperties map[string]interface{} `json:"updatedProperties"`

	// The board removed properties
	// required: false
	DeletedProperties []string `json:"deletedProperties"`

	// The board updated card properties
	// required: false
	UpdatedCardProperties []map[string]interface{} `json:"updatedCardProperties"`

	// The board removed card properties
	// required: false
	DeletedCardProperties []string `json:"deletedCardProperties"`
}

// BoardMember stores the information of the membership of a user on a board
// swagger:model
type BoardMember struct {
	// The ID of the board
	// required: true
	BoardID string `json:"boardId"`

	// The ID of the user
	// required: true
	UserID string `json:"userId"`

	// The independent roles of the user on the board
	// required: false
	Roles string `json:"roles"`

	// Minimum role because the board configuration
	// required: false
	MinimumRole string `json:"minimumRole"`

	// Marks the user as an admin of the board
	// required: true
	SchemeAdmin bool `json:"schemeAdmin"`

	// Marks the user as an editor of the board
	// required: true
	SchemeEditor bool `json:"schemeEditor"`

	// Marks the user as an commenter of the board
	// required: true
	SchemeCommenter bool `json:"schemeCommenter"`

	// Marks the user as an viewer of the board
	// required: true
	SchemeViewer bool `json:"schemeViewer"`

	// Marks the membership as generated by an access group
	// required: true
	Synthetic bool `json:"synthetic"`
}

// BoardMetadata contains metadata for a Board
// swagger:model
type BoardMetadata struct {
	// The ID for the board
	// required: true
	BoardID string `json:"boardId"`

	// The most recent time a descendant of this board was added, modified, or deleted
	// required: true
	DescendantLastUpdateAt int64 `json:"descendantLastUpdateAt"`

	// The earliest time a descendant of this board was added, modified, or deleted
	// required: true
	DescendantFirstUpdateAt int64 `json:"descendantFirstUpdateAt"`

	// The ID of the user that created the board
	// required: true
	CreatedBy string `json:"createdBy"`

	// The ID of the user that last modified the most recently modified descendant
	// required: true
	LastModifiedBy string `json:"lastModifiedBy"`
}

func BoardFromJSON(data io.Reader) *Board {
	var board *Board
	_ = json.NewDecoder(data).Decode(&board)
	return board
}

func BoardsFromJSON(data io.Reader) []*Board {
	var boards []*Board
	_ = json.NewDecoder(data).Decode(&boards)
	return boards
}

func BoardMemberFromJSON(data io.Reader) *BoardMember {
	var boardMember *BoardMember
	_ = json.NewDecoder(data).Decode(&boardMember)
	return boardMember
}

func BoardMembersFromJSON(data io.Reader) []*BoardMember {
	var boardMembers []*BoardMember
	_ = json.NewDecoder(data).Decode(&boardMembers)
	return boardMembers
}

func BoardMetadataFromJSON(data io.Reader) *BoardMetadata {
	var boardMetadata *BoardMetadata
	_ = json.NewDecoder(data).Decode(&boardMetadata)
	return boardMetadata
}

// Patch returns an updated version of the board.
func (p *BoardPatch) Patch(board *Board) *Board {
	if p.Type != nil {
		board.Type = *p.Type
	}

	if p.Title != nil {
		board.Title = *p.Title
	}

	if p.MinimumRole != nil {
		board.MinimumRole = *p.MinimumRole
	}

	if p.Description != nil {
		board.Description = *p.Description
	}

	if p.Icon != nil {
		board.Icon = *p.Icon
	}

	if p.ShowDescription != nil {
		board.ShowDescription = *p.ShowDescription
	}

	if p.ChannelID != nil {
		board.ChannelID = *p.ChannelID
	}

	for key, property := range p.UpdatedProperties {
		board.Properties[key] = property
	}

	for _, key := range p.DeletedProperties {
		delete(board.Properties, key)
	}

	if len(p.UpdatedCardProperties) != 0 || len(p.DeletedCardProperties) != 0 {
		// first we accumulate all properties indexed by, and maintain their order
		keyOrder := []string{}
		cardPropertyMap := map[string]map[string]interface{}{}
		for _, prop := range board.CardProperties {
			id, ok := prop["id"].(string)
			if !ok {
				// bad property, skipping
				continue
			}

			cardPropertyMap[id] = prop
			keyOrder = append(keyOrder, id)
		}

		// if there are properties marked for removal, we delete them
		for _, propertyID := range p.DeletedCardProperties {
			delete(cardPropertyMap, propertyID)
		}

		// if there are properties marked for update, we replace the
		// existing ones or add them
		for _, newprop := range p.UpdatedCardProperties {
			id, ok := newprop["id"].(string)
			if !ok {
				// bad new property, skipping
				continue
			}

			_, exists := cardPropertyMap[id]
			if !exists {
				keyOrder = append(keyOrder, id)
			}
			cardPropertyMap[id] = newprop
		}

		// and finally we flatten and save the updated properties
		newCardProperties := []map[string]interface{}{}
		for _, key := range keyOrder {
			p, exists := cardPropertyMap[key]
			if exists {
				newCardProperties = append(newCardProperties, p)
			}
		}

		board.CardProperties = newCardProperties
	}

	return board
}

func IsBoardTypeValid(t BoardType) bool {
	return t == BoardTypeOpen || t == BoardTypePrivate
}

func IsBoardMinimumRoleValid(r BoardRole) bool {
	return r == BoardRoleNone || r == BoardRoleAdmin || r == BoardRoleEditor || r == BoardRoleCommenter || r == BoardRoleViewer
}

func (p *BoardPatch) IsValid() error {
	if p.Type != nil && !IsBoardTypeValid(*p.Type) {
		return InvalidBoardErr{"invalid-board-type"}
	}

	if p.MinimumRole != nil && !IsBoardMinimumRoleValid(*p.MinimumRole) {
		return InvalidBoardErr{"invalid-board-minimum-role"}
	}

	return nil
}

type InvalidBoardErr struct {
	msg string
}

func (ibe InvalidBoardErr) Error() string {
	return ibe.msg
}

func (b *Board) IsValid() error {
	if b.TeamID == "" {
		return InvalidBoardErr{"empty-team-id"}
	}

	if !IsBoardTypeValid(b.Type) {
		return InvalidBoardErr{"invalid-board-type"}
	}

	if !IsBoardMinimumRoleValid(b.MinimumRole) {
		return InvalidBoardErr{"invalid-board-minimum-role"}
	}

	return nil
}

// BoardMemberHistoryEntry stores the information of the membership of a user on a board
// swagger:model
type BoardMemberHistoryEntry struct {
	// The ID of the board
	// required: true
	BoardID string `json:"boardId"`

	// The ID of the user
	// required: true
	UserID string `json:"userId"`

	// The action that added this history entry (created or deleted)
	// required: false
	Action string `json:"action"`

	// The insertion time
	// required: true
	InsertAt time.Time `json:"insertAt"`
}
