package db

import (
	"context"
	"fmt"
	"time"

	"git.haelnorr.com/h/golib/hwsauth"
	"github.com/bwmarrin/discordgo"
	"github.com/pkg/errors"
	"github.com/uptrace/bun"
)

var CurrentUser hwsauth.ContextLoader[*User]

type User struct {
	bun.BaseModel `bun:"table:users,alias:u"`

	ID        int    `bun:"id,pk,autoincrement"` // Integer ID (index primary key)
	Username  string `bun:"username,unique"`     // Username (unique)
	CreatedAt int64  `bun:"created_at"`          // Epoch timestamp when the user was added to the database
	DiscordID string `bun:"discord_id,unique"`
}

func (user *User) GetID() int {
	return user.ID
}

// Change the user's username
func (user *User) ChangeUsername(ctx context.Context, tx bun.Tx, newUsername string) error {
	_, err := tx.NewUpdate().
		Model(user).
		Set("username = ?", newUsername).
		Where("id = ?", user.ID).
		Exec(ctx)
	if err != nil {
		return errors.Wrap(err, "tx.Update")
	}
	user.Username = newUsername
	return nil
}

// CreateUser creates a new user with the given username and password
func CreateUser(ctx context.Context, tx bun.Tx, username string, discorduser *discordgo.User) (*User, error) {
	if discorduser == nil {
		return nil, errors.New("user cannot be nil")
	}
	user := &User{
		Username:  username,
		CreatedAt: time.Now().Unix(),
		DiscordID: discorduser.ID,
	}

	_, err := tx.NewInsert().
		Model(user).
		Exec(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "tx.Insert")
	}

	return user, nil
}

// GetUserByID queries the database for a user matching the given ID
// Returns nil, nil if no user is found
func GetUserByID(ctx context.Context, tx bun.Tx, id int) (*User, error) {
	fmt.Printf("user id requested: %v", id)
	user := new(User)
	err := tx.NewSelect().
		Model(user).
		Where("id = ?", id).
		Limit(1).
		Scan(ctx)
	if err != nil {
		if err.Error() == "sql: no rows in result set" {
			return nil, nil
		}
		return nil, errors.Wrap(err, "tx.Select")
	}
	return user, nil
}

// GetUserByUsername queries the database for a user matching the given username
// Returns nil, nil if no user is found
func GetUserByUsername(ctx context.Context, tx bun.Tx, username string) (*User, error) {
	user := new(User)
	err := tx.NewSelect().
		Model(user).
		Where("username = ?", username).
		Limit(1).
		Scan(ctx)
	if err != nil {
		if err.Error() == "sql: no rows in result set" {
			return nil, nil
		}
		return nil, errors.Wrap(err, "tx.Select")
	}
	return user, nil
}

// GetUserByDiscordID queries the database for a user matching the given discord id
// Returns nil, nil if no user is found
func GetUserByDiscordID(ctx context.Context, tx bun.Tx, discordID string) (*User, error) {
	user := new(User)
	err := tx.NewSelect().
		Model(user).
		Where("discord_id = ?", discordID).
		Limit(1).
		Scan(ctx)
	if err != nil {
		if err.Error() == "sql: no rows in result set" {
			return nil, nil
		}
		return nil, errors.Wrap(err, "tx.Select")
	}
	return user, nil
}

// IsUsernameUnique checks if the given username is unique (not already taken)
// Returns true if the username is available, false if it's taken
func IsUsernameUnique(ctx context.Context, tx bun.Tx, username string) (bool, error) {
	count, err := tx.NewSelect().
		Model((*User)(nil)).
		Where("username = ?", username).
		Count(ctx)
	if err != nil {
		return false, errors.Wrap(err, "tx.Count")
	}
	return count == 0, nil
}
