package database

import (
	"context"
	"database/sql"
	"errors"
	"log"

	"gitea.mrixs.me/minecraft-platform/backend/internal/models"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

var (
	ErrUserExists = errors.New("user with this username or email already exists")
)

type UserRepository struct {
	DB *pgxpool.Pool
}

// CreateUserTx создает нового пользователя и его профиль в рамках одной транзакции
func (r *UserRepository) CreateUserTx(ctx context.Context, user *models.User) error {
	log.Println("[DB] Beginning CreateUserTx transaction")
	tx, err := r.DB.Begin(ctx)
	if err != nil {
		log.Printf("[DB] ERROR: Failed to begin transaction: %v", err)
		return err
	}
	defer tx.Rollback(ctx)

	log.Println("[DB] Checking if user exists...")
	var exists bool
	err = tx.QueryRow(ctx,
		"SELECT EXISTS(SELECT 1 FROM users WHERE username = $1 OR email = $2)", user.Username, user.Email).Scan(&exists)
	if err != nil {
		log.Printf("[DB] ERROR: User existence check failed: %v", err)
		return err
	}
	if exists {
		log.Printf("[DB] User with username '%s' or email '%s' already exists.", user.Username, user.Email)
		return ErrUserExists
	}
	log.Println("[DB] User does not exist, proceeding.")

	log.Println("[DB] Inserting into 'users' table...")
	var newUserID int
	err = tx.QueryRow(ctx, "INSERT INTO users (uuid, username, email, password_hash, role) VALUES ($1, $2, $3, $4, $5) RETURNING id",
		user.UUID, user.Username, user.Email, user.PasswordHash, user.Role).Scan(&newUserID)
	if err != nil {
		log.Printf("[DB] ERROR: Insert into 'users' failed: %v", err)
		return err
	}
	log.Printf("[DB] Inserted into 'users' table successfully. New user ID: %d", newUserID)

	log.Println("[DB] Inserting into 'profiles' table...")
	_, err = tx.Exec(ctx, "INSERT INTO profiles (user_id) VALUES ($1)", newUserID)
	if err != nil {
		log.Printf("[DB] ERROR: Insert into 'profiles' failed: %v", err)
		return err
	}
	log.Println("[DB] Inserted into 'profiles' table successfully.")

	log.Println("[DB] Committing transaction...")
	return tx.Commit(ctx)
}

var (
	ErrUserNotFound = errors.New("user not found")
)

// GetUserByUsername находит пользователя по его имени.
// Возвращает полную структуру User, включая хеш пароля для проверки.
func (r *UserRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
	user := &models.User{}
	var userUUID string

	query := "SELECT id, uuid, username, email, password_hash, role FROM users WHERE username = $1"
	err := r.DB.QueryRow(ctx, query, username).Scan(
		&user.ID, &userUUID, &user.Username, &user.Email, &user.PasswordHash, &user.Role,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		return nil, err
	}

	user.UUID, _ = uuid.Parse(userUUID)
	return user, nil
}

// CreateAccessToken сохраняет новый токен доступа в базу данных.
func (r *UserRepository) CreateAccessToken(ctx context.Context, userID int, accessToken, clientToken string) error {
	query := "INSERT INTO access_tokens (user_id, access_token, client_token) VALUES ($1, $2, $3)"
	_, err := r.DB.Exec(ctx, query, userID, accessToken, clientToken)
	return err
}

// GetProfileByUUID находит пользователя и его профиль по UUID.
func (r *UserRepository) GetProfileByUUID(ctx context.Context, userUUID uuid.UUID) (*models.User, *models.Profile, error) {
	user := &models.User{UUID: userUUID}
	profile := &models.Profile{}
	var skinHash, capeHash sql.NullString

	query := `
		SELECT u.id, u.username, p.skin_hash, p.cape_hash
		FROM users u
		JOIN profiles p ON u.id = p.user_id
		WHERE u.uuid = $1`

	err := r.DB.QueryRow(ctx, query, userUUID).Scan(
		&user.ID, &user.Username, &skinHash, &capeHash,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil, ErrUserNotFound
		}
		return nil, nil, err
	}

	if skinHash.Valid {
		profile.SkinHash = skinHash.String
	}
	if capeHash.Valid {
		profile.CapeHash = capeHash.String
	}

	return user, profile, nil
}

var (
	ErrTokenNotFound = errors.New("access token not found or invalid")
)

// ValidateAccessToken проверяет, действителен ли токен для данного пользователя.
func (r *UserRepository) ValidateAccessToken(ctx context.Context, token string, userUUID uuid.UUID) error {
	var exists bool
	query := `
		SELECT EXISTS (
			SELECT 1
			FROM access_tokens at
			JOIN users u ON at.user_id = u.id
			WHERE at.access_token = $1 AND u.uuid = $2
		)`

	err := r.DB.QueryRow(ctx, query, token, userUUID).Scan(&exists)
	if err != nil {
		return err
	}

	if !exists {
		return ErrTokenNotFound
	}

	return nil
}

// UpdateSkinHash обновляет хеш скина для пользователя.
func (r *UserRepository) UpdateSkinHash(ctx context.Context, userID int, skinHash string) error {
	query := "UPDATE profiles SET skin_hash = $1, updated_at = NOW() WHERE user_id = $2"
	result, err := r.DB.Exec(ctx, query, skinHash, userID)
	if err != nil {
		return err
	}

	if result.RowsAffected() == 0 {
		return ErrUserNotFound
	}

	return nil
}

// GetUserByLogin находит пользователя по его имени или email.
func (r *UserRepository) GetUserByLogin(ctx context.Context, login string) (*models.User, error) {
	user := &models.User{}
	var userUUID string

	query := "SELECT id, uuid, username, email, password_hash, role, created_at, updated_at FROM users WHERE username = $1 OR email = $1"
	err := r.DB.QueryRow(ctx, query, login).Scan(
		&user.ID, &userUUID, &user.Username, &user.Email, &user.PasswordHash, &user.Role, &user.CreatedAt, &user.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		return nil, err
	}

	user.UUID, _ = uuid.Parse(userUUID)
	return user, nil
}

// GetAllUsers возвращает список всех пользователей.
func (r *UserRepository) GetAllUsers(ctx context.Context) ([]models.User, error) {
	rows, err := r.DB.Query(ctx, "SELECT id, uuid, username, email, role, created_at, updated_at FROM users ORDER BY id")
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var users []models.User
	for rows.Next() {
		var u models.User
		var userUUID string
		if err := rows.Scan(&u.ID, &userUUID, &u.Username, &u.Email, &u.Role, &u.CreatedAt, &u.UpdatedAt); err != nil {
			return nil, err
		}
		u.UUID, _ = uuid.Parse(userUUID)
		users = append(users, u)
	}
	return users, nil
}

// UpdateUserRole обновляет роль пользователя по его ID.
func (r *UserRepository) UpdateUserRole(ctx context.Context, userID int, newRole string) error {
	res, err := r.DB.Exec(ctx, "UPDATE users SET role = $1 WHERE id = $2", newRole, userID)
	if err != nil {
		return err
	}
	if res.RowsAffected() == 0 {
		return ErrUserNotFound
	}
	return nil
}

// GetUserByID находит пользователя по его ID.
func (r *UserRepository) GetUserByID(ctx context.Context, userID int) (*models.User, error) {
	user := &models.User{}
	var userUUID string

	query := "SELECT id, uuid, username, email, password_hash, role, created_at, updated_at FROM users WHERE id = $1"
	err := r.DB.QueryRow(ctx, query, userID).Scan(
		&user.ID, &userUUID, &user.Username, &user.Email, &user.PasswordHash, &user.Role, &user.CreatedAt, &user.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		return nil, err
	}

	user.UUID, _ = uuid.Parse(userUUID)
	return user, nil
}
