package models

import (
	"errors"
	"strings"
	"time"

	"code.nonshy.com/nonshy/website/pkg/log"
)

// UserNote table where users can write private notes about each other.
type UserNote struct {
	ID          uint64 `gorm:"primaryKey"`
	UserID      uint64 `gorm:"index"` // author ID
	AboutUserID uint64 `gorm:"index"` // target user ID
	Message     string
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// GetNoteBetweenUsers finds the existing note or creates an empty model if not found.
func GetNoteBetweenUsers(currentUser *User, user *User) *UserNote {
	var (
		note   = &UserNote{}
		result = DB.Model(note).Where(
			"user_id = ? AND about_user_id = ?",
			currentUser.ID, user.ID,
		).First(&note)
	)
	if result.Error != nil {
		note = &UserNote{
			UserID:      currentUser.ID,
			AboutUserID: user.ID,
		}
	}
	return note
}

// GetNote finds a user note by its ID.
func GetNote(id uint64) (*UserNote, error) {
	p := &UserNote{}
	result := DB.First(&p, id)
	return p, result.Error
}

// CountNotesAboutUser returns the number of notes (the current user) has about the other user.
//
// For regular user, will return zero or one; for admins, will return the total count of notes
// left by any other users about this user.
func CountNotesAboutUser(currentUser *User, user *User) int64 {
	var (
		wheres       = []string{}
		placeholders = []interface{}{}
		count        int64
	)

	if currentUser.IsAdmin {
		wheres = append(wheres, "about_user_id = ?")
		placeholders = append(placeholders, user.ID)
	} else {
		wheres = append(wheres, "user_id = ? AND about_user_id = ?")
		placeholders = append(placeholders, currentUser.ID, user.ID)
	}

	query := DB.Model(&UserNote{}).Where(
		strings.Join(wheres, " AND "),
		placeholders...,
	).Count(&count)
	if query.Error != nil {
		log.Error("CountNotesAboutUser(%s, %s): %s", currentUser.Username, user.Username, query.Error)
	}

	return count
}

// PaginateUserNotes shows all notes written by users about the target user.
func PaginateUserNotes(user *User, pager *Pagination) ([]*UserNote, error) {
	var (
		notes        = []*UserNote{}
		wheres       = []string{}
		placeholders = []interface{}{}
	)

	wheres = append(wheres, "about_user_id = ?")
	placeholders = append(placeholders, user.ID)

	query := DB.Joins("JOIN users ON users.id = user_notes.user_id").Where(
		strings.Join(wheres, " AND "),
		placeholders...,
	).Order(
		"users.is_admin desc, " + pager.Sort,
	)

	query.Model(&UserNote{}).Count(&pager.Total)

	result := query.Offset(
		pager.GetOffset(),
	).Limit(pager.PerPage).Find(&notes)

	return notes, result.Error
}

// PaginateMyUserNotes shows all notes written by the current user about others.
func PaginateMyUserNotes(currentUser *User, search string, pager *Pagination) ([]*UserNote, error) {
	var (
		notes        = []*UserNote{}
		wheres       = []string{}
		placeholders = []interface{}{}
		ilike        = "%" + search + "%"
	)

	wheres = append(wheres, "user_notes.user_id = ?")
	placeholders = append(placeholders, currentUser.ID)

	// Searching?
	if search != "" {
		wheres = append(wheres, "(users.username ILIKE ? OR user_notes.message ILIKE ?)")
		placeholders = append(placeholders, ilike, ilike)
	}

	query := DB.Joins("JOIN users ON users.id = user_notes.about_user_id").Where(
		strings.Join(wheres, " AND "),
		placeholders...,
	).Order(
		pager.Sort,
	)

	query.Model(&UserNote{}).Count(&pager.Total)

	result := query.Offset(
		pager.GetOffset(),
	).Limit(pager.PerPage).Find(&notes)

	return notes, result.Error
}

// Save the note.
func (p *UserNote) Save() error {
	if p.ID == 0 {
		return DB.Create(p).Error
	}
	return DB.Save(p).Error
}

var ErrNoUserNoteToDelete = errors.New("you had no user note to delete")

// Delete the DB entry.
func (p *UserNote) Delete() error {
	if p.ID == 0 {
		return ErrNoUserNoteToDelete
	}
	return DB.Delete(p).Error
}
