package exporting

import (
	"archive/zip"
	"fmt"

	"code.nonshy.com/nonshy/website/pkg/log"
	"code.nonshy.com/nonshy/website/pkg/models"
	"gorm.io/gorm"
)

// ExportModels is the entry point function to export all data tables about a user.
func ExportModels(zw *zip.Writer, user *models.User) error {
	type task struct {
		Step string
		Fn   func(*zip.Writer, *models.User) error
	}

	// List of tables to export. Keep the ordering in sync with
	// the AutoMigrate() calls in ../models.go
	var todo = []task{
		{"User", ExportUserTable},
		{"ProfileField", ExportProfileFieldTable},
		{"Photo", ExportPhotoTable},
		{"PrivatePhoto", ExportPrivatePhotoTable},
		{"CertificationPhoto", ExportCertificationPhotoTable},
		{"Message", ExportMessageTable},
		{"Friend", ExportFriendTable},
		{"Block", ExportBlockTable},
		{"Feedback", ExportFeedbackTable},
		{"Forum", ExportForumTable},
		{"Thread", ExportThreadTable},
		{"Comment", ExportCommentTable},
		{"Like", ExportLikeTable},
		{"Notification", ExportNotificationTable},
		{"Subscription", ExportSubscriptionTable},
		{"CommentPhoto", ExportCommentPhotoTable},
		// Note: Poll table is eager-loaded in Thread export
		{"PollVote", ExportPollVoteTable},
		// Note: AdminGroup info is eager-loaded in User export
		{"UserLocation", ExportUserLocationTable},
		{"UserNote", ExportUserNoteTable},
		{"TwoFactor", ExportTwoFactorTable},
	}
	for _, item := range todo {
		log.Info("Exporting data model: %s", item.Step)
		if err := item.Fn(zw, user); err != nil {
			return fmt.Errorf("%s: %s", item.Step, err)
		}
	}

	return nil
}

func ExportUserTable(zw *zip.Writer, user *models.User) error {
	return ZipJson(zw, "user.json", user)
}

func ExportProfileFieldTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.ProfileField{}
		query = models.DB.Model(&models.ProfileField{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "profile_fields.json", items)
}

func ExportPhotoTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Photo{}
		query = models.DB.Model(&models.Photo{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	// Copy all the images into the ZIP.
	for _, row := range items {
		if row.Filename != "" {
			if err := ZipPhoto(zw, "photos", row.Filename); err != nil {
				return err
			}
		}
		if row.CroppedFilename != "" {
			if err := ZipPhoto(zw, "profile_photos", row.CroppedFilename); err != nil {
				return err
			}
		}
	}

	return ZipJson(zw, "photos.json", items)
}

func ExportPrivatePhotoTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.PrivatePhoto{}
		query = models.DB.Model(&models.PrivatePhoto{}).Where(
			"source_user_id = ? OR target_user_id = ?",
			user.ID, user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "private_photos.json", items)
}

func ExportCertificationPhotoTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.CertificationPhoto{}
		query = models.DB.Model(&models.CertificationPhoto{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	// Copy all the images into the ZIP.
	for _, row := range items {
		if row.Filename != "" {
			if err := ZipPhoto(zw, "certification_photo", row.Filename); err != nil {
				return err
			}
		}
	}

	return ZipJson(zw, "certification_photo.json", items)
}

func ExportMessageTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Message{}
		query = models.DB.Model(&models.Message{}).Where(
			"source_user_id = ? OR target_user_id = ?",
			user.ID, user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "messages.json", items)
}

func ExportFriendTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Friend{}
		query = models.DB.Model(&models.Friend{}).Where(
			"source_user_id = ? OR target_user_id = ?",
			user.ID, user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "friends.json", items)
}

func ExportBlockTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Block{}
		query = models.DB.Model(&models.Block{}).Where(
			"source_user_id = ? OR target_user_id = ?",
			user.ID, user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "blocks.json", items)
}

func ExportFeedbackTable(zw *zip.Writer, user *models.User) error {
	var (
		items       = []*models.Feedback{}
		photoIDs, _ = user.AllPhotoIDs()
		query       *gorm.DB
	)

	// If they have photos, query on those.
	if len(photoIDs) > 0 {
		query = models.DB.Model(&models.Feedback{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?) OR (table_name = 'photos' AND table_id IN ?)",
			user.ID, user.ID, photoIDs,
		).Find(&items)
	} else {
		// Only reports about their user.
		query = models.DB.Model(&models.Feedback{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?)",
			user.ID, user.ID,
		).Find(&items)
	}

	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "feedback.json", items)
}

func ExportForumTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Forum{}
		query = models.DB.Model(&models.Forum{}).Where(
			"owner_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "forums.json", items)
}

func ExportThreadTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Thread{}
		query = (&models.Thread{}).Preload().Joins(
			"JOIN comments ON (comments.id = threads.comment_id)",
		).Where(
			"comments.user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "threads.json", items)
}

func ExportCommentTable(zw *zip.Writer, user *models.User) error {
	var (
		items       = []*models.Comment{}
		photoIDs, _ = user.AllPhotoIDs()
		query       *gorm.DB
	)

	// If they have photos, query on those.
	if len(photoIDs) > 0 {
		query = models.DB.Model(&models.Comment{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?) OR (table_name = 'photos' AND table_id IN ?)",
			user.ID, user.ID, photoIDs,
		).Find(&items)
	} else {
		query = models.DB.Model(&models.Comment{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?)",
			user.ID, user.ID,
		).Find(&items)
	}

	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "comments.json", items)
}

func ExportLikeTable(zw *zip.Writer, user *models.User) error {
	var (
		items       = []*models.Like{}
		photoIDs, _ = user.AllPhotoIDs()
		query       *gorm.DB
	)

	// If they have photos, query on those.
	if len(photoIDs) > 0 {
		query = models.DB.Model(&models.Like{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?) OR (table_name = 'photos' AND table_id IN ?)",
			user.ID, user.ID, photoIDs,
		).Find(&items)
	} else {
		// Only reports about their user.
		query = models.DB.Model(&models.Like{}).Where(
			"user_id = ? OR (table_name = 'users' AND table_id = ?)",
			user.ID, user.ID,
		).Find(&items)
	}

	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "likes.json", items)
}

func ExportNotificationTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Notification{}
		query = models.DB.Model(&models.Notification{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "notifications.json", items)
}

func ExportSubscriptionTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.Subscription{}
		query = models.DB.Model(&models.Subscription{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "subscriptions.json", items)
}

func ExportCommentPhotoTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.CommentPhoto{}
		query = models.DB.Model(&models.CommentPhoto{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	// Copy all the images into the ZIP.
	for _, row := range items {
		if row.Filename != "" {
			if err := ZipPhoto(zw, "comment_photos", row.Filename); err != nil {
				return err
			}
		}
	}

	return ZipJson(zw, "comment_photos.json", items)
}

func ExportPollVoteTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.PollVote{}
		query = (&models.PollVote{}).Preload().Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "poll_votes.json", items)
}

func ExportUserNoteTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.UserNote{}
		query = models.DB.Model(&models.UserNote{}).Where(
			"user_id = ? OR about_user_id = ?",
			user.ID, user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "user_notes.json", items)
}

func ExportUserLocationTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.UserLocation{}
		query = models.DB.Model(&models.UserLocation{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "user_location.json", items)
}

func ExportTwoFactorTable(zw *zip.Writer, user *models.User) error {
	var (
		items = []*models.TwoFactor{}
		query = models.DB.Model(&models.TwoFactor{}).Where(
			"user_id = ?",
			user.ID,
		).Find(&items)
	)
	if query.Error != nil {
		return query.Error
	}

	return ZipJson(zw, "two_factor.json", items)
}
