package signserver

import (
	"strings"

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
)

// SignCharacterRepository implements SignCharacterRepo with PostgreSQL.
type SignCharacterRepository struct {
	db *sqlx.DB
}

// NewSignCharacterRepository creates a new SignCharacterRepository.
func NewSignCharacterRepository(db *sqlx.DB) *SignCharacterRepository {
	return &SignCharacterRepository{db: db}
}

func (r *SignCharacterRepository) CountNewCharacters(uid uint32) (int, error) {
	var count int
	err := r.db.QueryRow("SELECT COUNT(*) FROM characters WHERE user_id = $1 AND is_new_character = true", uid).Scan(&count)
	return count, err
}

func (r *SignCharacterRepository) CreateCharacter(uid uint32, lastLogin uint32) error {
	var charID int
	err := r.db.QueryRow(`
		INSERT INTO characters (
			user_id, is_female, is_new_character, name, unk_desc_string,
			hr, gr, weapon_type, last_login)
		VALUES($1, False, True, '', '', 0, 0, 0, $2)
		RETURNING id`,
		uid, lastLogin,
	).Scan(&charID)
	if err != nil {
		return err
	}
	_, err = r.db.Exec(`INSERT INTO user_binary (id) VALUES ($1)`, charID)
	return err
}

func (r *SignCharacterRepository) GetForUser(uid uint32) ([]character, error) {
	characters := make([]character, 0)
	err := r.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id", uid)
	if err != nil {
		return nil, err
	}
	return characters, nil
}

func (r *SignCharacterRepository) IsNewCharacter(cid int) (bool, error) {
	var isNew bool
	err := r.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", cid).Scan(&isNew)
	return isNew, err
}

func (r *SignCharacterRepository) HardDelete(cid int) error {
	_, err := r.db.Exec("DELETE FROM characters WHERE id = $1", cid)
	return err
}

func (r *SignCharacterRepository) SoftDelete(cid int) error {
	_, err := r.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", cid)
	return err
}

// GetFriends returns friends for a character using parameterized queries
// (fixes the SQL injection vector from the original string-concatenated approach).
func (r *SignCharacterRepository) GetFriends(charID uint32) ([]members, error) {
	var friendsCSV string
	err := r.db.QueryRow("SELECT friends FROM characters WHERE id=$1", charID).Scan(&friendsCSV)
	if err != nil {
		return nil, err
	}
	if friendsCSV == "" {
		return nil, nil
	}

	friendsSlice := strings.Split(friendsCSV, ",")
	// Filter out empty strings
	ids := make([]string, 0, len(friendsSlice))
	for _, s := range friendsSlice {
		s = strings.TrimSpace(s)
		if s != "" {
			ids = append(ids, s)
		}
	}
	if len(ids) == 0 {
		return nil, nil
	}

	// Use parameterized ANY($1) instead of string-concatenated WHERE id=X OR id=Y
	friends := make([]members, 0)
	err = r.db.Select(&friends, "SELECT id, name FROM characters WHERE id = ANY($1)", pq.Array(ids))
	if err != nil {
		return nil, err
	}
	return friends, nil
}

// GetGuildmates returns guildmates for a character.
func (r *SignCharacterRepository) GetGuildmates(charID uint32) ([]members, error) {
	var inGuild int
	err := r.db.QueryRow("SELECT count(*) FROM guild_characters WHERE character_id=$1", charID).Scan(&inGuild)
	if err != nil {
		return nil, err
	}
	if inGuild == 0 {
		return nil, nil
	}

	var guildID int
	err = r.db.QueryRow("SELECT guild_id FROM guild_characters WHERE character_id=$1", charID).Scan(&guildID)
	if err != nil {
		return nil, err
	}

	guildmates := make([]members, 0)
	err = r.db.Select(&guildmates, "SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=$1 AND character_id!=$2", guildID, charID)
	if err != nil {
		return nil, err
	}
	return guildmates, nil
}
