package api

import (
	"errors"
	"time"

	"code.justin.tv/feeds/feeds-common/entity"
)

const (
	// Enum values for container property "require_review":

	// RequireReviewNone indicates that messages created for this container are
	// immediately published.
	RequireReviewNone = "none"
	// RequireReviewAll indicates that messages created for this container are
	// intially pending review.
	RequireReviewAll = "all"

	// Enum values for message change property "action_type":
	// ActionTypeEdit is the message change action when a message is edited.
	ActionTypeEdit = "edit"
	// ActionTypeDelete is the message change action when a message is deleted.
	ActionTypeDelete = "delete"
)

var (
	ErrLikelySpam                 = errors.New("The message is determined to likely be spam")
	ErrContainsBannedWord         = errors.New("The message contains one or more banned words")
	ErrSenderIsSuspendedOrDeleted = errors.New("The message sender is suspended or deleted")
	ErrSenderHasUnverifiedEmail   = errors.New("The message sender does not have a verified email")
	ErrSenderIsChannelBanned      = errors.New("The message sender is banned in the container owner's channel")
	ErrSenderIsIgnored            = errors.New("The message sender is ignored by the container owner")
	ErrEnforcementRuleFailed      = errors.New("The message fails one of the provided enforcement rules")
)

// MessageRisk contains metadata around the likelihood the message is intended
// to be abusive or spammy.
type MessageRisk struct {
	// SpamLikelihood is the likelihood the message is spam.
	SpamLikelihood float64 `json:"spam_likelihood"`
	// IsAutoModClean indicates whether AutoMod has flagged the message as clean.
	IsAutoModClean bool `json:"is_automod_clean"`
	// RuleResponses gives the individual responses from each rule
	RuleResponses map[string]string `json:"rule_responses"`
	// OverallResponse gives the aggregate response of all the rules
	OverallResponse string `json:"overall_response"`
}

// MessageContainer contains Metadata about the container to which to the message was sent
type MessageContainer struct {
	// FollowersOnlyDuration tells you how long the minimum following time is
	FollowersOnlyDuration *time.Duration `json:"followers_only_duration"`
}

// MessageContent contains metadata around the message text.
type MessageContent struct {
	Text      string                   `json:"text"`
	Fragments []MessageContentFragment `json:"fragments"`

	// Deprecated
	Emoticons []MessageContentEmoticon `json:"emoticons,omitempty"`
}

// MessageContentV2 contains metadata around the message text.
type MessageContentV2 struct {
	Text      string                   `json:"text"`
	Fragments []MessageContentFragment `json:"fragments"`
}

// MessageContentFragment contains metadata around a fragment of the message text.
// At most one of the MessageContentFragment* fields should be set to indicate
// the type of fragment and store metadata. If no optional fields are set,
// the fragment is assumed to be plaintext.
type MessageContentFragment struct {
	// Text is the snippet from the original message text for this fragment.
	// If a caller does not know how to handle a particular type, it should
	// fallback to simply displaying this Text field.
	Text string `json:"text"`

	Emoticon *MessageContentFragmentEmoticon `json:"emoticon,omitempty"`
	Link     *MessageContentFragmentLink     `json:"link,omitempty"`
	Mention  *MessageContentFragmentMention  `json:"mention,omitempty"`
}

// MessageContentFragmentEmoticon represents an fragment of the message text
// that should be replaced by an emoticon image.
type MessageContentFragmentEmoticon struct {
	EmoticonID    string `json:"id"`
	EmoticonSetID string `json:"set_id"`
}

// MessageContentFragmentLink represents a fragment of the message text that represents a web URL.
type MessageContentFragmentLink struct {
	Host string `json:"host"`
	// EmbedMetadata is populated asynchronously in a call to Shine.
	EmbedMetadata *MessageContentFragmentLinkEmbed `json:"embed_metadata,omitempty"`
}

// MessageContentFragmentLinkEmbed contains metadata that represents a rich media link embed.
// It is not guaranteed but likely to include title, description, author information, and a thumbnail.
// If the link is a Twitch media link, there may be additional metadata.
type MessageContentFragmentLinkEmbed struct {
	ErrorCode  string `json:"error_code,omitempty"`
	RequestURL string `json:"request_url"`
	// Type may be "photo", "video", "link" or "rich", per the Oembed spec
	Type         string `json:"type"`
	Title        string `json:"title"`
	Description  string `json:"description"`
	AuthorName   string `json:"author_name,omitempty"`
	AuthorURL    string `json:"author_url,omitempty"`
	ThumbnailURL string `json:"thumbnail_url"`
	// Provider is the original source of the link, e.g. "YouTube" or "Imgur"
	ProviderName string `json:"provider_name"`

	TwitchMetadata *TwitchEmbedMetadata `json:"twitch_metadata,omitempty"`
}

// TwitchEmbedMetadata contains additional rich media metadata for some types of Twitch URLs.
// This is currently supported for clip, VOD, stream, or event links.
type TwitchEmbedMetadata struct {
	// TwitchType may currently be "clip", "vod", "stream", or "event"
	TwitchType string `json:"twitch_type"`

	Clip   *TwitchClipMetadata   `json:"clip_metadata,omitempty"`
	VOD    *TwitchVODMetadata    `json:"vod_metadata,omitempty"`
	Stream *TwitchStreamMetadata `json:"stream_metadata,omitempty"`
	Event  *TwitchEventMetadata  `json:"event_metadata,omitempty"`
}

// TwitchClipMetadata contains embed metadata for Twitch clips.
type TwitchClipMetadata struct {
	BroadcasterID      string     `json:"broadcaster_id"`
	BroadcasterLogin   string     `json:"broadcaster_login"`
	CreatedAt          *time.Time `json:"created_at"`
	Game               string     `json:"game"`
	Slug               string     `json:"slug"`
	VideoLengthSeconds int        `json:"video_length"`
	ViewCount          int        `json:"view_count"`
}

// TwitchVODMetadata contains embed metadata for Twitch VODs.
type TwitchVODMetadata struct {
	BroadcasterID      string     `json:"broadcaster_id"`
	BroadcasterLogin   string     `json:"broadcaster_login"`
	CreatedAt          *time.Time `json:"created_at"`
	Game               string     `json:"game"`
	VideoLengthSeconds int        `json:"video_length"`
	ViewCount          int        `json:"view_count"`
}

// TwitchStreamMetadata contains embed metadata for Twitch streams.
type TwitchStreamMetadata struct {
	BroadcasterID    string     `json:"broadcaster_id"`
	BroadcasterLogin string     `json:"broadcaster_login"`
	CreatedAt        *time.Time `json:"created_at"`
	Game             string     `json:"game"`
	StreamType       string     `json:"stream_type"`
	ViewCount        int        `json:"view_count"`
}

// TwitchEventMetadata contains embed metadata for Twitch events.
type TwitchEventMetadata struct {
	AuthorID    string     `json:"author_id"`
	AuthorLogin string     `json:"author_login"`
	Game        string     `json:"game"`
	StartTime   *time.Time `json:"start_time"`
	EndTime     *time.Time `json:"end_time"`
}

// MessageContentFragmentMention represents a fragment of the message that
// mentions a user.
type MessageContentFragmentMention struct {
	UserID      string `json:"user_id"`
	Login       string `json:"login"`
	DisplayName string `json:"display_name"`
}

// MessageContentEmoticon represents an emoticon found within message text.
// Ranges are 0-indexed and inclusive: [start,end]
//
// Deprecated: Use MessageContent.Fragments to parse the message instead.
type MessageContentEmoticon struct {
	ID    string `json:"id"`
	SetID string `json:"set_id"`
	Start int    `json:"start"`
	End   int    `json:"end"`
}

// MessageSender contains metadata around the user that sends a message.
type MessageSender struct {
	UserID      string      `json:"user_id"`
	Login       string      `json:"login"`
	DisplayName string      `json:"display_name"`
	ChatColor   string      `json:"chat_color"`
	Badges      []UserBadge `json:"badges"`
}

// UserBadge represents a chat badge for a user.
type UserBadge struct {
	ID      string `json:"id"`
	Version string `json:"version"`
}

// MessageResult is a (Message, cursor) pair returned when a response expects a list of messages.
type MessageResult struct {
	Message Message `json:"message"`
	Cursor  string  `json:"cursor"`
}

// Message defines a user-generated message object.
type Message struct {
	ID          string         `json:"id"`
	ContainerID string         `json:"container_id"`
	Content     MessageContent `json:"content"`
	Sender      MessageSender  `json:"sender"`

	// SentAt is the time the message was originally received by the server.
	SentAt time.Time `json:"sent_at"`
	// EditedAt is the time the message was last edited.
	// Nil if the message was never edited.
	EditedAt *time.Time `json:"edited_at,omitempty"`
	// DeletedAt is the time the message was deleted.
	// Nil if the message is not deleted.
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
	// IsPublished indicates whether a message is publicly viewable.
	IsPublished bool `json:"is_published"`
	// IsPendingReview indicates whether a message is awaiting moderation review.
	IsPendingReview bool `json:"is_pending_review"`
	// History is the list of changes to this message's content.
	History []MessageChange `json:"history,omitempty"`
	// BitsAmount is the amount of bits sent with this message.
	BitsAmount int `json:"bits_amount,omitempty"`
	// Databag is arbitrary metadata defined by the feature service.
	Databag Databag `json:"databag,omitempty"`
}

// MessageChange contains metadata around a change to an existing message,
// such as an edit or delete.
type MessageChange struct {
	MessageID       string    `json:"message_id"`
	UserID          string    `json:"user_id"`
	ActionType      string    `json:"action_type"`
	UpdatedAt       time.Time `json:"updated_at"`
	OriginalMessage Message   `json:"original_message"`
}

// Container defines a parent object of one or more messages.
type Container struct {
	ID string `json:"id"`

	Namespace  string `json:"namespace"`
	UniqueName string `json:"unique_name"`

	// Owner is the object (likely a User) that owns this container. The owner
	// is relevant for applying chat rules like banned words.
	Owner *entity.Entity `json:"owner_id,omitempty"`
	// RequireReview is an enum that represents whether messages in this container
	// should be published by default.
	RequireReview string `json:"require_review"`
	// LastMessage is the last message sent to this container. Nil if no message
	// has been sent or the last message is set on the container view object.
	LastMessage *Message `json:"last_message,omitempty"`
	// DeletedAt is the time the container was deleted. Nil if the container is
	// not deleted.
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
	// Databag is arbitrary metadata defined by the feature service.
	Databag Databag `json:"databag,omitempty"`
}

// ContainerView defines user-specific properties of a container.
type ContainerView struct {
	UserID string `json:"user_id"`
	// ID of the container the view belongs to
	ContainerID string `json:"container_id"`
	// IsArchived indicates whether this user wants to hide this container
	// until a new message appears.
	IsArchived bool `json:"is_archived"`
	// IsMuted indicates whether this user wants to ignore all notifications for
	// new messages to this container.
	IsMuted bool `json:"is_muted"`
	// LastReadAt is the time thie user has last acknowledged this container.
	// This time may or may not line up with a message SentAt time.
	LastReadAt time.Time `json:"last_read_at"`
	// UserSendCount is the number of times this user has sent a message to this
	// container.
	UserSendCount int `json:"user_send_count"`
	// UserLastSentAt is the time this user has last sent a message to this
	// container.
	UserLastSentAt time.Time `json:"user_last_sent_at"`
	// LastMessage is the last message sent to this container. Nil if no message
	// has been sent or the last message is set on the container object.
	LastMessage *Message `json:"last_message,omitempty"`
	// JoinedAt is the time this user joined this container.
	JoinedAt time.Time `json:"joined_at"`
	// HasUnreadMessages indicates whether this container has messages this user
	// has not read.
	HasUnreadMessages bool `json:"has_unread_messages"`
	// UnreadNoticeCount is the number of messages that require the user's attention.
	UnreadNoticeCount int `json:"unread_notice_count"`
}

// Databag is a map that contains arbitrary keys and values defined by
// the feature service. It is intended to be used as metadata storage and it's
// properties should not affect business logic within the messaging platform.
type Databag map[string]interface{}

const (
	// DatabagSet sets the value of a field. Overrides if the field already exists.
	DatabagSet = "set"
	// DatabagDelete deletes the value of a field. Noop if the field doesn't exist.
	DatabagDelete = "delete"
)

// DatabagFieldUpdate represents a request to update a databag field.
type DatabagFieldUpdate struct {
	Action string      `json:"action"`
	Name   string      `json:"name"`
	Value  interface{} `json:"value"`
}
