package models

import (
	"errors"
	"strings"
	"time"

	"gorm.io/gorm"
)

// Forum table.
type Forum struct {
	ID           uint64 `gorm:"primaryKey"`
	OwnerID      uint64 `gorm:"index"`
	Owner        User   `gorm:"foreignKey:owner_id"`
	Category     string `gorm:"index"`
	Fragment     string `gorm:"uniqueIndex"`
	Title        string
	Description  string
	Explicit     bool `gorm:"index"`
	Privileged   bool
	PermitPhotos bool
	InnerCircle  bool
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

// Preload related tables for the forum (classmethod).
func (f *Forum) Preload() *gorm.DB {
	return DB.Preload("Owner")
}

// GetForum by ID.
func GetForum(id uint64) (*Forum, error) {
	forum := &Forum{}
	result := forum.Preload().First(&forum, id)
	return forum, result.Error
}

// GetForums queries a set of thread IDs and returns them mapped.
func GetForums(IDs []uint64) (map[uint64]*Forum, error) {
	var (
		mt = map[uint64]*Forum{}
		fs = []*Forum{}
	)

	result := (&Forum{}).Preload().Where("id IN ?", IDs).Find(&fs)
	for _, row := range fs {
		mt[row.ID] = row
	}

	return mt, result.Error
}

// ForumByFragment looks up a forum by its URL fragment.
func ForumByFragment(fragment string) (*Forum, error) {
	if fragment == "" {
		return nil, errors.New("the URL fragment is required")
	}

	var (
		f      = &Forum{}
		result = f.Preload().Where(
			"fragment = ?",
			fragment,
		).First(&f)
	)

	return f, result.Error
}

/*
PaginateForums scans over the available forums for a user.

Parameters:

- userID: of who is looking
- categories: optional, filter within categories
- pager
*/
func PaginateForums(user *User, categories []string, pager *Pagination) ([]*Forum, error) {
	var (
		fs           = []*Forum{}
		query        = (&Forum{}).Preload()
		wheres       = []string{}
		placeholders = []interface{}{}
	)

	if len(categories) > 0 {
		wheres = append(wheres, "category IN ?")
		placeholders = append(placeholders, categories)
	}

	// Hide explicit forum if user hasn't opted into it.
	if !user.Explicit && !user.IsAdmin {
		wheres = append(wheres, "explicit = false")
	}

	// Hide circle forums if the user isn't in the circle.
	if !user.IsInnerCircle() {
		wheres = append(wheres, "inner_circle is not true")
	}

	// Filters?
	if len(wheres) > 0 {
		query = query.Where(
			strings.Join(wheres, " AND "),
			placeholders...,
		)
	}

	query = query.Order(pager.Sort)
	query.Model(&Forum{}).Count(&pager.Total)
	result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs)
	return fs, result.Error
}

// PaginateOwnedForums returns forums the user owns (or all forums to admins).
func PaginateOwnedForums(userID uint64, isAdmin bool, pager *Pagination) ([]*Forum, error) {
	var (
		fs    = []*Forum{}
		query = (&Forum{}).Preload()
	)

	if !isAdmin {
		query = query.Where(
			"owner_id = ?",
			userID,
		)
	}

	query = query.Order(pager.Sort)
	query.Model(&Forum{}).Count(&pager.Total)
	result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs)
	return fs, result.Error
}

// CreateForum.
func CreateForum(f *Forum) error {
	result := DB.Create(f)
	return result.Error
}

// Save a forum.
func (f *Forum) Save() error {
	return DB.Save(f).Error
}

// CategorizedForum supports the main index page with custom categories.
type CategorizedForum struct {
	Category string
	Forums   []*Forum
}

// CategorizeForums buckets forums into categories for front-end.
func CategorizeForums(fs []*Forum, categories []string) []*CategorizedForum {
	var (
		result = []*CategorizedForum{}
		idxMap = map[string]int{}
	)

	// Initialize the result set.
	for i, category := range categories {
		result = append(result, &CategorizedForum{
			Category: category,
			Forums:   []*Forum{},
		})
		idxMap[category] = i
	}

	// Bucket the forums into their categories.
	for _, forum := range fs {
		category := forum.Category
		if category == "" {
			continue
		}
		idx := idxMap[category]
		result[idx].Forums = append(result[idx].Forums, forum)
	}

	// Remove any blank categories with no boards.
	var filtered = []*CategorizedForum{}
	for _, forum := range result {
		if len(forum.Forums) == 0 {
			continue
		}
		filtered = append(filtered, forum)
	}

	return filtered
}
