package models

import (
	"strings"
	"time"
)

// Message table.
type Message struct {
	ID           uint64 `gorm:"primaryKey"`
	SourceUserID uint64 `gorm:"index"`
	TargetUserID uint64 `gorm:"index"`
	Read         bool   `gorm:"index"`
	Message      string
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

// GetMessage by ID.
func GetMessage(id uint64) (*Message, error) {
	m := &Message{}
	result := DB.First(&m, id)
	return m, result.Error
}

// GetMessages for a user.
func GetMessages(user *User, sent bool, pager *Pagination) ([]*Message, error) {
	var (
		m              = []*Message{}
		blockedUserIDs = BlockedUserIDs(user)
		where          = []string{}
		placeholders   = []interface{}{}
	)

	if sent {
		where = append(where, "source_user_id = ?")
		placeholders = append(placeholders, user.ID)

		if len(blockedUserIDs) > 0 {
			where = append(where, "target_user_id NOT IN ?")
			placeholders = append(placeholders, blockedUserIDs)
		}
	} else {
		where = append(where, "target_user_id = ?")
		placeholders = append(placeholders, user.ID)

		if len(blockedUserIDs) > 0 {
			where = append(where, "source_user_id NOT IN ?")
			placeholders = append(placeholders, blockedUserIDs)
		}
	}

	// Don't show messages from banned or disabled accounts.
	where = append(where, `
		NOT EXISTS (
			SELECT 1
			FROM users
			WHERE users.id IN (messages.target_user_id, messages.source_user_id)
			AND users.status <> 'active'
		)
	`)

	query := DB.Where(
		strings.Join(where, " AND "),
		placeholders...,
	).Order(pager.Sort)

	query.Model(&Message{}).Count(&pager.Total)
	result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&m)
	return m, result.Error
}

// GetMessageThread returns paginated message history between two people.
func GetMessageThread(sourceUserID, targetUserID uint64, pager *Pagination) ([]*Message, error) {
	var m = []*Message{}

	query := DB.Where(
		"(source_user_id = ? AND target_user_id = ?) OR (source_user_id = ? AND target_user_id = ?)",
		sourceUserID, targetUserID,
		targetUserID, sourceUserID,
	).Order(pager.Sort)

	query.Model(&Message{}).Count(&pager.Total)
	result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&m)
	return m, result.Error
}

// HasMessageThread returns if a message thread exists between two users (either direction).
// Returns the ID of the thread and a boolean OK that it existed.
func HasMessageThread(a, b *User) (uint64, bool) {
	var pager = &Pagination{
		Page:    1,
		PerPage: 1,
		Sort:    "updated_at desc",
	}
	messages, err := GetMessageThread(a.ID, b.ID, pager)
	if err == nil && len(messages) > 0 {
		return messages[0].ID, true
	}
	return 0, false
}

// DeleteMessageThread removes all message history between two people.
func DeleteMessageThread(message *Message) error {
	return DB.Where(
		"(source_user_id = ? AND target_user_id = ?) OR (source_user_id = ? AND target_user_id = ?)",
		message.SourceUserID, message.TargetUserID,
		message.TargetUserID, message.SourceUserID,
	).Delete(&Message{}).Error
}

// CountUnreadMessages gets the count of unread messages for a user.
func CountUnreadMessages(user *User) (int64, error) {
	var (
		blockedUserIDs = BlockedUserIDs(user)
		where          = []string{
			"target_user_id = ? AND read = ?",
		}
		placeholders = []interface{}{
			user.ID, false,
		}
	)

	// Blocking user IDs?
	if len(blockedUserIDs) > 0 {
		where = append(where, "source_user_id NOT IN ?")
		placeholders = append(placeholders, blockedUserIDs)
	}

	// Don't show messages from banned or disabled accounts.
	where = append(where, `
		NOT EXISTS (
			SELECT 1
			FROM users
			WHERE users.id IN (messages.target_user_id, messages.source_user_id)
			AND users.status <> 'active'
		)
	`)

	query := DB.Where(
		strings.Join(where, " AND "),
		placeholders...,
	)

	var count int64
	result := query.Model(&Message{}).Count(&count)
	return count, result.Error
}

// SendMessage from a source to a target user.
func SendMessage(sourceUserID, targetUserID uint64, message string) (*Message, error) {
	m := &Message{
		SourceUserID: sourceUserID,
		TargetUserID: targetUserID,
		Message:      message,
		Read:         false,
	}

	result := DB.Create(m)
	return m, result.Error
}

// Save message.
func (m *Message) Save() error {
	result := DB.Save(m)
	return result.Error
}

// Delete a message.
func (m *Message) Delete() error {
	return DB.Delete(m).Error
}
