package deletion

import (
	"fmt"

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

// DeleteUser wipes a user and all associated data from the database.
func DeleteUser(user *models.User) error {
	log.Error("BEGIN DeleteUser(%d, %s)", user.ID, user.Username)

	// Remove all linked tables and assets.
	type remover struct {
		Step string
		Fn   func(uint64) error
	}

	// Blank out the user's profile photo ID to avoid conflict removing their picture.
	user.RemoveProfilePhoto()

	// Tables to remove. In case of any unexpected DB errors, these tables are ordered
	// to remove the "safest" fields first.
	var todo = []remover{
		{"Notifications", DeleteNotifications},
		{"Likes", DeleteLikes},
		{"Threads", DeleteForumThreads},
		{"Comments", DeleteComments},
		{"Subscriptions", DeleteSubscriptions},
		{"Photos", DeleteUserPhotos},
		{"Private Photo Grants", DeletePrivateGrants},
		{"Certification Photo", DeleteCertification},
		{"Who's Nearby Locations", DeleteUserLocation},
		{"Comment Photos", DeleteUserCommentPhotos},
		{"Messages", DeleteUserMessages},
		{"Friends", DeleteFriends},
		{"Blocks", DeleteBlocks},
		{"Feedbacks", DeleteFeedbacks},
		{"Two Factor", DeleteTwoFactor},
		{"Profile Fields", DeleteProfile},
		{"User Notes", DeleteUserNotes},
	}
	for _, item := range todo {
		if err := item.Fn(user.ID); err != nil {
			return fmt.Errorf("%s: %s", item.Step, err)
		}
	}

	// Remove the user itself.
	return user.Delete()
}

// DeleteUserPhotos scrubs data for deleting a user.
func DeleteUserPhotos(userID uint64) error {
	log.Error("DeleteUser: BEGIN DeleteUserPhotos(%d)", userID)

	// Deeply scrub all user photos.
	pager := &models.Pagination{
		Page:    1,
		PerPage: 20,
		Sort:    "photos.id",
	}

	for {
		photos, err := models.PaginateUserPhotos(
			userID,
			models.UserGallery{
				Visibility: models.PhotoVisibilityAll,
			},
			pager,
		)

		if err != nil {
			return err
		}

		if len(photos) == 0 {
			break
		}

		for _, item := range photos {
			log.Warn("DeleteUserPhotos(%d): remove file %s", userID, item.Filename)
			photo.Delete(item.Filename)
			if item.CroppedFilename != "" {
				log.Warn("DeleteUserPhotos(%d): remove file %s", userID, item.CroppedFilename)
				photo.Delete(item.CroppedFilename)
			}
			if err := item.Delete(); err != nil {
				return err
			}
		}
	}

	log.Error("DeleteUser: END DeleteUserPhotos(%d)", userID)
	return nil
}

// DeleteUserCommentPhotos scrubs data for deleting a user.
func DeleteUserCommentPhotos(userID uint64) error {
	log.Error("DeleteUser: BEGIN DeleteUserCommentPhotos(%d)", userID)

	// Deeply scrub all user photos.
	pager := &models.Pagination{
		Page:    1,
		PerPage: 20,
		Sort:    "comment_photos.id",
	}

	for {
		photos, err := models.PaginateUserCommentPhotos(
			userID,
			pager,
		)

		if err != nil {
			return err
		}

		if len(photos) == 0 {
			break
		}

		for _, item := range photos {
			log.Warn("DeleteUserCommentPhotos(%d): remove file %s", userID, item.Filename)
			photo.Delete(item.Filename)
			if err := item.Delete(); err != nil {
				return err
			}
		}
	}

	log.Error("DeleteUser: END DeleteUserPhotos(%d)", userID)
	return nil
}

// DeleteCertification scrubs data for deleting a user.
func DeleteCertification(userID uint64) error {
	log.Error("DeleteUser: DeleteCertification(%d)", userID)
	if cert, err := models.GetCertificationPhoto(userID); err == nil {
		if cert.Filename != "" {
			log.Warn("DeleteCertification(%d): remove file %s", userID, cert.Filename)
			photo.Delete(cert.Filename)
		}
		return cert.Delete()
	}
	return nil
}

// DeleteTwoFactor scrubs data for deleting a user.
func DeleteTwoFactor(userID uint64) error {
	log.Error("DeleteUser: DeleteTwoFactor(%d)", userID)
	result := models.DB.Where(
		"user_id = ?",
		userID,
	).Delete(&models.TwoFactor{})
	return result.Error
}

// DeleteUserLocation scrubs data for deleting a user.
func DeleteUserLocation(userID uint64) error {
	log.Error("DeleteUser: DeleteUserLocation(%d)", userID)
	result := models.DB.Where(
		"user_id = ?",
		userID,
	).Delete(&models.UserLocation{})
	return result.Error
}

// DeleteUserMessages scrubs data for deleting a user.
func DeleteUserMessages(userID uint64) error {
	log.Error("DeleteUser: DeleteUserMessages(%d)", userID)
	result := models.DB.Where(
		"source_user_id = ? OR target_user_id = ?",
		userID, userID,
	).Delete(&models.Message{})
	return result.Error
}

// DeleteFriends scrubs data for deleting a user.
func DeleteFriends(userID uint64) error {
	log.Error("DeleteUser: DeleteUserFriends(%d)", userID)
	result := models.DB.Where(
		"source_user_id = ? OR target_user_id = ?",
		userID, userID,
	).Delete(&models.Friend{})
	return result.Error
}

// DeleteBlocks scrubs data for deleting a user.
func DeleteBlocks(userID uint64) error {
	log.Error("DeleteUser: DeleteBlocks(%d)", userID)
	result := models.DB.Where(
		"source_user_id = ? OR target_user_id = ?",
		userID, userID,
	).Delete(&models.Block{})
	return result.Error
}

// DeleteFeedbacks scrubs data for deleting a user.
func DeleteFeedbacks(userID uint64) error {
	log.Error("DeleteUser: DeleteFeedbacks(%d)", userID)
	result := models.DB.Where(
		"user_id = ? OR (table_name='users' AND table_id=?)",
		userID, userID,
	).Delete(&models.Feedback{})
	return result.Error
}

// DeletePrivateGrants scrubs data for deleting a user.
func DeletePrivateGrants(userID uint64) error {
	log.Error("DeleteUser: DeletePrivateGrants(%d)", userID)
	result := models.DB.Where(
		"source_user_id = ? OR target_user_id = ?",
		userID, userID,
	).Delete(&models.PrivatePhoto{})
	return result.Error
}

// DeleteNotifications scrubs all notifications about a user.
func DeleteNotifications(userID uint64) error {
	log.Error("DeleteUser: DeleteNotifications(%d)", userID)
	result := models.DB.Where(
		"user_id = ? OR about_user_id = ?",
		userID, userID,
	).Delete(&models.Notification{})
	return result.Error
}

// DeleteSubscriptions scrubs all notification subscriptions about a user.
func DeleteSubscriptions(userID uint64) error {
	log.Error("DeleteUser: DeleteSubscriptions(%d)", userID)
	result := models.DB.Where(
		"user_id = ?",
		userID,
	).Delete(&models.Subscription{})
	return result.Error
}

// DeleteLikes scrubs all Likes about a user.
func DeleteLikes(userID uint64) error {
	log.Error("DeleteUser: DeleteLikes(%d)", userID)
	result := models.DB.Where(
		"user_id = ? OR (table_name='users' AND table_id=?)",
		userID, userID,
	).Delete(&models.Like{})
	return result.Error
}

// DeleteProfile scrubs data for deleting a user.
func DeleteProfile(userID uint64) error {
	log.Error("DeleteUser: DeleteProfile(%d)", userID)
	result := models.DB.Where(
		"user_id = ?",
		userID,
	).Delete(&models.ProfileField{})
	return result.Error
}

// DeleteForumThreads scrubs all forum threads started by the user.
func DeleteForumThreads(userID uint64) error {
	log.Error("DeleteUser: DeleteForumThreads(%d)", userID)

	var threadIDs = []uint64{}
	result := models.DB.Table(
		"threads",
	).Joins(
		"JOIN comments ON (threads.comment_id = comments.id)",
	).Select(
		"distinct(threads.id) as id",
	).Where(
		"comments.user_id = ?",
		userID,
	).Scan(&threadIDs)

	if result.Error != nil {
		return fmt.Errorf("Couldn't list thread IDs created by user: %s", result.Error)
	}

	log.Warn("thread IDs to wipe: %+v", threadIDs)

	// Wipe all these threads and their comments.
	if len(threadIDs) > 0 {
		// First, delete the threads - so they won't be referring to comment_ids that we
		// delete next and causing errors to arise.
		result = models.DB.Where(
			"id IN ?",
			threadIDs,
		).Delete(&models.Thread{})
		if result.Error != nil {
			return fmt.Errorf("Couldn't delete your forum threads: %s", result.Error)
		}

		// Remove the comments.
		result = models.DB.Where(
			"table_name = ? AND table_id IN ?",
			"threads", threadIDs,
		).Delete(&models.Comment{})
		if result.Error != nil {
			return fmt.Errorf("Couldn't wipe threads of comments: %s", result.Error)
		}

		return result.Error
	}

	return nil
}

// DeleteComments deletes all comments by the user.
func DeleteComments(userID uint64) error {
	log.Error("DeleteUser: DeleteComments(%d)", userID)

	result := models.DB.Where(
		"user_id = ?",
		userID,
	).Delete(&models.Comment{})
	return result.Error
}

// DeleteUserNotes scrubs data for deleting a user.
func DeleteUserNotes(userID uint64) error {
	log.Error("DeleteUser: DeleteUserNotes(%d)", userID)
	result := models.DB.Where(
		"user_id = ? OR about_user_id = ?",
		userID, userID,
	).Delete(&models.UserNote{})
	return result.Error
}
