package api

import (
	"fmt"
	"net/http"
	"strconv"

	"code.nonshy.com/nonshy/website/pkg/config"
	"code.nonshy.com/nonshy/website/pkg/log"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/session"
)

// Likes API posts a new like on something.
func Likes() http.HandlerFunc {
	// Request JSON schema.
	type Request struct {
		TableName string `json:"name"`
		TableID   string `json:"id"`
		Unlike    bool   `json:"unlike,omitempty"`
		Referrer  string `json:"page"`
	}

	// Response JSON schema.
	type Response struct {
		OK    bool   `json:"OK"`
		Error string `json:"error,omitempty"`
		Likes int64  `json:"likes"`
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			SendJSON(w, http.StatusNotAcceptable, Response{
				Error: "POST method only",
			})
			return
		}

		// Get the current user.
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: "Couldn't get current user!",
			})
			return
		}

		// Parse request payload.
		var req Request
		if err := ParseJSON(r, &req); err != nil {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: fmt.Sprintf("Error with request payload: %s", err),
			})
			return
		}

		// Sanity check things. The page= param (Referrer) must be a relative URL, the path
		// is useful for "liked your comment" notifications to supply the Link URL for the
		// notification.
		if len(req.Referrer) > 0 && req.Referrer[0] != '/' {
			req.Referrer = ""
		}

		// Is the ID an integer?
		var tableID uint64
		if v, err := strconv.Atoi(req.TableID); err != nil {
			// Non-integer must be usernames?
			if req.TableName == "users" {
				user, err := models.FindUser(req.TableID)
				if err != nil {
					SendJSON(w, http.StatusBadRequest, Response{
						Error: "User not found.",
					})
					return
				}
				tableID = user.ID
			} else {
				SendJSON(w, http.StatusBadRequest, Response{
					Error: "Invalid ID.",
				})
				return
			}
		} else {
			tableID = uint64(v)
		}

		// Who do we notify about this like?
		var (
			targetUser          *models.User
			notificationMessage string
		)
		switch req.TableName {
		case "photos":
			if photo, err := models.GetPhoto(tableID); err == nil {
				if user, err := models.GetUser(photo.UserID); err == nil {
					// Admin safety check: in case the admin clicked 'Like' on a friends-only or private
					// picture they shouldn't have been expected to see, do not log a like.
					if currentUser.IsAdmin && currentUser.ID != user.ID {
						if (photo.Visibility == models.PhotoFriends && !models.AreFriends(user.ID, currentUser.ID)) ||
							(photo.Visibility == models.PhotoPrivate && !models.IsPrivateUnlocked(user.ID, currentUser.ID)) {
							SendJSON(w, http.StatusForbidden, Response{
								Error: "You are not allowed to like that photo.",
							})
							return
						}
					}

					// Blocking safety check: if either user blocks the other, liking is not allowed.
					if models.IsBlocking(currentUser.ID, user.ID) {
						SendJSON(w, http.StatusForbidden, Response{
							Error: "You are not allowed to like that photo.",
						})
						return
					}
					targetUser = user
				}
			} else {
				log.Error("For like on photos table: didn't find photo %d: %s", tableID, err)
			}
		case "users":
			log.Error("subject is users, find %d", tableID)
			if user, err := models.GetUser(tableID); err == nil {
				targetUser = user
				log.Warn("found user %s", targetUser.Username)

				// Blocking safety check: if either user blocks the other, liking is not allowed.
				if models.IsBlocking(currentUser.ID, user.ID) {
					SendJSON(w, http.StatusForbidden, Response{
						Error: "You are not allowed to like that profile.",
					})
					return
				}
			} else {
				log.Error("For like on users table: didn't find user %d: %s", tableID, err)
			}
		case "comments":
			log.Error("subject is comments, find %d", tableID)
			if comment, err := models.GetComment(tableID); err == nil {
				targetUser = &comment.User
				notificationMessage = comment.Message
				log.Warn("found user %s", targetUser.Username)

				// Blocking safety check: if either user blocks the other, liking is not allowed.
				if models.IsBlocking(currentUser.ID, targetUser.ID) {
					SendJSON(w, http.StatusForbidden, Response{
						Error: "You are not allowed to like that comment.",
					})
					return
				}
			} else {
				log.Error("For like on users table: didn't find user %d: %s", tableID, err)
			}
		}

		// Is the table likeable?
		if _, ok := models.LikeableTables[req.TableName]; !ok {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: fmt.Sprintf("Can't like table %s: not allowed.", req.TableName),
			})
			return
		}

		// Put in a like.
		if req.Unlike {
			if err := models.Unlike(currentUser, req.TableName, tableID); err != nil {
				SendJSON(w, http.StatusBadRequest, Response{
					Error: fmt.Sprintf("Error unliking: %s", err),
				})
				return
			}

			// Remove the target's notification about this like.
			models.RemoveSpecificNotification(targetUser.ID, models.NotificationLike, req.TableName, tableID)
		} else {
			if err := models.AddLike(currentUser, req.TableName, tableID); err != nil {
				SendJSON(w, http.StatusBadRequest, Response{
					Error: fmt.Sprintf("Error liking: %s", err),
				})
				return
			}

			// Notify the recipient of the like.
			log.Info("Added like on %s:%d, notifying owner %+v", req.TableName, tableID, targetUser)
			if targetUser != nil && !targetUser.NotificationOptOut(config.NotificationOptOutLikes) {
				notif := &models.Notification{
					UserID:    targetUser.ID,
					AboutUser: *currentUser,
					Type:      models.NotificationLike,
					TableName: req.TableName,
					TableID:   tableID,
					Message:   notificationMessage,
					Link:      req.Referrer,
				}
				if err := models.CreateNotification(notif); err != nil {
					log.Error("Couldn't create Likes notification: %s", err)
				}
			}
		}

		// Send success response.
		SendJSON(w, http.StatusOK, Response{
			OK:    true,
			Likes: models.CountLikes(req.TableName, tableID),
		})
	})
}

// WhoLikes API checks who liked something.
func WhoLikes() http.HandlerFunc {
	// Response JSON schema.
	type Liker struct {
		Username     string                  `json:"username"`
		Avatar       string                  `json:"avatar"`
		Relationship models.UserRelationship `json:"relationship"`
	}
	type Response struct {
		OK    bool               `json:"OK"`
		Error string             `json:"error,omitempty"`
		Likes []Liker            `json:"likes,omitempty"`
		Pager *models.Pagination `json:"pager,omitempty"`
		Pages int                `json:"pages,omitempty"`
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			SendJSON(w, http.StatusNotAcceptable, Response{
				Error: "GET method only",
			})
			return
		}

		// Parse request parameters.
		var (
			tableName  = r.FormValue("table_name")
			tableID, _ = strconv.Atoi(r.FormValue("table_id"))
			page, _    = strconv.Atoi(r.FormValue("page"))
		)
		if tableName == "" {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: "Missing required table_name",
			})
			return
		} else if tableID == 0 {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: "Missing required table_id",
			})
			return
		}

		if page < 1 {
			page = 1
		}

		// Get the current user.
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			SendJSON(w, http.StatusBadRequest, Response{
				Error: "Couldn't get current user!",
			})
			return
		}

		// Get a page of users who've liked this.
		var pager = &models.Pagination{
			Page:    page,
			PerPage: config.PageSizeLikeList,
			Sort:    "created_at desc",
		}
		users, err := models.PaginateLikes(currentUser, tableName, uint64(tableID), pager)
		if err != nil {
			SendJSON(w, http.StatusInternalServerError, Response{
				Error: fmt.Sprintf("Error getting likes: %s", err),
			})
			return
		}

		// Map user data to just the essentials for front-end.
		var result = []Liker{}
		for _, user := range users {
			result = append(result, Liker{
				Username:     user.Username,
				Avatar:       user.VisibleAvatarURL(currentUser),
				Relationship: user.UserRelationship,
			})
		}

		// Send success response.
		SendJSON(w, http.StatusOK, Response{
			OK:    true,
			Likes: result,
			Pager: pager,
			Pages: pager.Pages(),
		})
	})
}
