package models

import (
	"database/sql"
	"time"

	"gorm.io/gen"
	"gorm.io/gorm"
)

// A user describes an account for creating content and events.
// This may be controlled by either a human or some external script
//
// Data stored externally:
// - Feed connections (which note belongs in the feed of this user, for what reason), see [NoteToFeed]
type User struct {
	// ID is a uuid for this account
	//
	// NOTE: Performance and storage wise, using a UUID (as string) for the primary key
	// is not the best idea and it would be better to use incrementing integers
	// Additionally, there is a risk with inconsistency if postgres crashes during a commit
	// in which case the new entry might not have a valid Id with which it could be found
	// and the username is lost until manual recovery
	// However, a UUID is still necessary in some way to provide a (secondary) stable
	// identifier for users and other servers, especially when changing the username
	// (username != display name) might be a future feature
	// Same also applies for other types that use a UUID as primary key
	ID string `gorm:"primarykey;default:gen_random_uuid()" json:"id"`
	// Username of the user (eg "max" if the full username is @max@example.com)
	// Assume unchangable (once set by a user) to be kind to other implementations
	// Would be an easy avenue to fuck with them though
	Username  string    `gorm:"unique"                               json:"username"`
	CreatedAt time.Time `                                            json:"created_at"` // When this entry was created. Automatically set by gorm
	// When this account was last updated. Will also be used for refreshing remote accounts. Automatically set by gorm
	UpdatedAt time.Time `                                            json:"updated_at"`
	// When this entry was deleted (for soft deletions)
	// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
	// If not null, this entry is marked as deleted
	DeletedAt    gorm.DeletedAt `gorm:"index"                                json:"deleted_at"`
	Server       RemoteServer   `                                            json:"-"`
	ServerId     uint           `                                            json:"server_id"`    // Id of the server this user is from, needed for including RemoteServer
	DisplayName  string         `                                            json:"display_name"` // The display name of the user. Can be different from the handle
	Description  string         `                                            json:"description"`  // The description of a user account
	IsBot        bool           `                                            json:"is_bot"`       // Whether to mark this account as a script controlled one
	Icon         *MediaMetadata `                                            json:"-"`
	IconId       sql.NullString `                                            json:"icon_id"` // ID of a media file used as icon
	Background   *MediaMetadata `                                            json:"-"`
	BackgroundId sql.NullString `                                            json:"background_id"` // ID of a media file used as background image
	Banner       *MediaMetadata `                                            json:"-"`
	BannerId     sql.NullString `                                            json:"banner_id"`      // ID of a media file used as banner
	Indexable    bool           `                                            json:"indexable"`      // Whether this account can be found by crawlers
	PublicKeyRsa []byte         `                                            json:"public_key_rsa"` // The public RSA key of the account
	PublicKeyEd  []byte         `                                            json:"public_key_ed"`  // The public Ed25519 key of the account
	// Whether this account restricts following
	// If true, the owner must approve of a follow request first
	RestrictedFollow bool `                                            json:"restricted_follow"`

	Location sql.NullString `json:"location"`
	Birthday sql.NullTime   `json:"birthday"`

	// Whether the account got verified and is allowed to be active
	// For local accounts being active means being allowed to login and perform interactions
	// For remote users, if an account is not verified, any interactions it sends are discarded
	Verified bool `json:"verified"`
	// 64 byte unique id for passkeys, because UUIDs are 128 bytes and passkey spec says 64 bytes max
	// In theory, could also slash Id in half, but that would be a lot more calculations than the
	// saved space is worth
	PasskeyId            []byte `json:"-"`
	FinishedRegistration bool   `json:"-"` // Whether this account has completed registration yet
	PrivateKeyRsa        []byte `json:"-"`
	PrivateKeyEd         []byte `json:"-"`

	// ---- "Remote" linked values
	InfoFields  []UserInfoField      `json:"-"`
	BeingTypes  []UserToBeing        `json:"-"`
	Tags        []UserToTag          `json:"-"`
	Relations   []UserToUserRelation `json:"-"`
	Pronouns    []UserToPronoun      `json:"-"`
	Roles       []UserToRole         `json:"-"`
	RemoteInfo  *UserRemoteLinks     `json:"-"`
	AuthMethods []UserAuthMethod     `json:"-"`
}

type IUser interface {
	// Get a user by a username, ignoring all restrictions on that user
	//
	// SELECT * FROM @@table WHERE username = @username AND deleted_at IS NULL LIMIT 1
	GetByUsernameUnrestricted(username string) (*gen.T, error)

	// Get a user by the username.
	// Restricted to users visible to ActivityPub
	//
	// SELECT * FROM @@table WHERE
	//   username = @username AND
	//   deleted_at IS NULL AND
	//   finished_registration = true AND
	//   verified = true
	// LIMIT 1
	GetByUsername(username string) (*gen.T, error)

	// Get all true public accounts (verified & no restricted follow & indexable)
	// in a paged manner, sorted by date saved
	//
	// SELECT * FROM @@table WHERE
	//   deleted_at IS NULL AND
	//   verified = true AND
	//   restricted_follow = false AND
	//   indexable = true
	// ORDER BY created_at ASC
	// LIMIT 50
	// OFFSET @pageNr * 50
	GetPagedTruePublic(pageNr uint) ([]gen.T, error)

	// Get all deleted accounts in a paged manner, sorted by date saved
	//
	// SELECT * FROM @@table WHERE
	//   deleted_at IS NOT NULL AND
	// ORDER BY created_at ASC
	// LIMIT 50
	// OFFSET @pageNr * 50
	GetPagedAllDeleted(pageNr uint) ([]gen.T, error)

	// Get all accounts that aren't deleted in a paged manner, sorted by date saved
	//
	// SELECT * FROM @@table WHERE
	//   deleted_at IS NULL
	// ORDER BY created_at ASC
	// LIMIT 50
	// OFFSET @pageNr * 50
	GetPagedAllNonDeleted(pageNr uint) ([]gen.T, error)

	// Gdpr deleted users
	//
	// DELETE FROM @@table WHERE deleted_at IS NOT NULL AND deleted_at + interval '30 days' < NOW()
	GdprUsers() error
}
