package signserver

import (
	"database/sql"
	"testing"
	"time"

	cfg "erupe-ce/config"

	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
)

func TestCharacterStruct(t *testing.T) {
	c := character{
		ID:             12345,
		IsFemale:       true,
		IsNewCharacter: false,
		Name:           "TestHunter",
		UnkDescString:  "Test description",
		HR:             999,
		GR:             300,
		WeaponType:     5,
		LastLogin:      1700000000,
	}

	if c.ID != 12345 {
		t.Errorf("ID = %d, want 12345", c.ID)
	}
	if c.IsFemale != true {
		t.Error("IsFemale should be true")
	}
	if c.IsNewCharacter != false {
		t.Error("IsNewCharacter should be false")
	}
	if c.Name != "TestHunter" {
		t.Errorf("Name = %s, want TestHunter", c.Name)
	}
	if c.UnkDescString != "Test description" {
		t.Errorf("UnkDescString = %s, want Test description", c.UnkDescString)
	}
	if c.HR != 999 {
		t.Errorf("HR = %d, want 999", c.HR)
	}
	if c.GR != 300 {
		t.Errorf("GR = %d, want 300", c.GR)
	}
	if c.WeaponType != 5 {
		t.Errorf("WeaponType = %d, want 5", c.WeaponType)
	}
	if c.LastLogin != 1700000000 {
		t.Errorf("LastLogin = %d, want 1700000000", c.LastLogin)
	}
}

func TestCharacterStructDefaults(t *testing.T) {
	c := character{}

	if c.ID != 0 {
		t.Errorf("default ID = %d, want 0", c.ID)
	}
	if c.IsFemale != false {
		t.Error("default IsFemale should be false")
	}
	if c.IsNewCharacter != false {
		t.Error("default IsNewCharacter should be false")
	}
	if c.Name != "" {
		t.Errorf("default Name = %s, want empty", c.Name)
	}
	if c.HR != 0 {
		t.Errorf("default HR = %d, want 0", c.HR)
	}
	if c.GR != 0 {
		t.Errorf("default GR = %d, want 0", c.GR)
	}
	if c.WeaponType != 0 {
		t.Errorf("default WeaponType = %d, want 0", c.WeaponType)
	}
}

func TestMembersStruct(t *testing.T) {
	m := members{
		CID:  100,
		ID:   200,
		Name: "FriendName",
	}

	if m.CID != 100 {
		t.Errorf("CID = %d, want 100", m.CID)
	}
	if m.ID != 200 {
		t.Errorf("ID = %d, want 200", m.ID)
	}
	if m.Name != "FriendName" {
		t.Errorf("Name = %s, want FriendName", m.Name)
	}
}

func TestMembersStructDefaults(t *testing.T) {
	m := members{}

	if m.CID != 0 {
		t.Errorf("default CID = %d, want 0", m.CID)
	}
	if m.ID != 0 {
		t.Errorf("default ID = %d, want 0", m.ID)
	}
	if m.Name != "" {
		t.Errorf("default Name = %s, want empty", m.Name)
	}
}

func TestCharacterWeaponTypes(t *testing.T) {
	weaponTypes := []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}

	for _, wt := range weaponTypes {
		c := character{WeaponType: wt}
		if c.WeaponType != wt {
			t.Errorf("WeaponType = %d, want %d", c.WeaponType, wt)
		}
	}
}

func TestCharacterHRRange(t *testing.T) {
	tests := []struct {
		name string
		hr   uint16
	}{
		{"min", 0},
		{"beginner", 1},
		{"hr30", 30},
		{"hr50", 50},
		{"hr99", 99},
		{"hr299", 299},
		{"hr998", 998},
		{"hr999", 999},
		{"max uint16", 65535},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := character{HR: tt.hr}
			if c.HR != tt.hr {
				t.Errorf("HR = %d, want %d", c.HR, tt.hr)
			}
		})
	}
}

func TestCharacterGRRange(t *testing.T) {
	tests := []struct {
		name string
		gr   uint16
	}{
		{"min", 0},
		{"gr1", 1},
		{"gr100", 100},
		{"gr300", 300},
		{"gr999", 999},
		{"max uint16", 65535},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := character{GR: tt.gr}
			if c.GR != tt.gr {
				t.Errorf("GR = %d, want %d", c.GR, tt.gr)
			}
		})
	}
}

func TestCharacterIDRange(t *testing.T) {
	tests := []struct {
		name string
		id   uint32
	}{
		{"min", 0},
		{"small", 1},
		{"medium", 1000000},
		{"large", 0xFFFFFFFF},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := character{ID: tt.id}
			if c.ID != tt.id {
				t.Errorf("ID = %d, want %d", c.ID, tt.id)
			}
		})
	}
}

func TestCharacterGender(t *testing.T) {
	male := character{IsFemale: false}
	if male.IsFemale != false {
		t.Error("Male character should have IsFemale = false")
	}

	female := character{IsFemale: true}
	if female.IsFemale != true {
		t.Error("Female character should have IsFemale = true")
	}
}

func TestCharacterNewStatus(t *testing.T) {
	newChar := character{IsNewCharacter: true}
	if newChar.IsNewCharacter != true {
		t.Error("New character should have IsNewCharacter = true")
	}

	existingChar := character{IsNewCharacter: false}
	if existingChar.IsNewCharacter != false {
		t.Error("Existing character should have IsNewCharacter = false")
	}
}

func TestCharacterNameLength(t *testing.T) {
	names := []string{
		"",
		"A",
		"Hunter",
		"LongHunterName123",
	}

	for _, name := range names {
		c := character{Name: name}
		if c.Name != name {
			t.Errorf("Name = %s, want %s", c.Name, name)
		}
	}
}

func TestCharacterLastLogin(t *testing.T) {
	tests := []struct {
		name      string
		lastLogin uint32
	}{
		{"zero", 0},
		{"past", 1600000000},
		{"present", 1700000000},
		{"future", 1800000000},
		{"max", 0xFFFFFFFF},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := character{LastLogin: tt.lastLogin}
			if c.LastLogin != tt.lastLogin {
				t.Errorf("LastLogin = %d, want %d", c.LastLogin, tt.lastLogin)
			}
		})
	}
}

func TestMembersCIDAssignment(t *testing.T) {
	m := members{CID: 12345}
	if m.CID != 12345 {
		t.Errorf("CID = %d, want 12345", m.CID)
	}
}

func TestMultipleCharacters(t *testing.T) {
	chars := []character{
		{ID: 1, Name: "Char1", HR: 100},
		{ID: 2, Name: "Char2", HR: 200},
		{ID: 3, Name: "Char3", HR: 300},
	}

	for i, c := range chars {
		expectedID := uint32(i + 1)
		if c.ID != expectedID {
			t.Errorf("chars[%d].ID = %d, want %d", i, c.ID, expectedID)
		}
	}
}

func TestMultipleMembers(t *testing.T) {
	membersList := []members{
		{CID: 1, ID: 10, Name: "Friend1"},
		{CID: 1, ID: 20, Name: "Friend2"},
		{CID: 2, ID: 30, Name: "Friend3"},
	}

	if membersList[0].CID != membersList[1].CID {
		t.Error("First two members should share the same CID")
	}

	if membersList[1].CID == membersList[2].CID {
		t.Error("Third member should have different CID")
	}
}

func TestGetCharactersForUser(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		characters: []character{
			{ID: 1, IsFemale: false, Name: "Hunter1", HR: 100, GR: 50, WeaponType: 3, LastLogin: 1700000000},
			{ID: 2, IsFemale: true, Name: "Hunter2", HR: 200, GR: 100, WeaponType: 7, LastLogin: 1700000001},
		},
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars, err := server.getCharactersForUser(1)
	if err != nil {
		t.Errorf("getCharactersForUser() error: %v", err)
	}
	if len(chars) != 2 {
		t.Errorf("getCharactersForUser() returned %d characters, want 2", len(chars))
	}
	if chars[0].Name != "Hunter1" {
		t.Errorf("First character name = %s, want Hunter1", chars[0].Name)
	}
	if chars[1].IsFemale != true {
		t.Error("Second character should be female")
	}
}

func TestGetCharactersForUserNoCharacters(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		characters: []character{},
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars, err := server.getCharactersForUser(1)
	if err != nil {
		t.Errorf("getCharactersForUser() error: %v", err)
	}
	if len(chars) != 0 {
		t.Errorf("getCharactersForUser() returned %d characters, want 0", len(chars))
	}
}

func TestGetCharactersForUserDBError(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		getForUserErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	_, err := server.getCharactersForUser(1)
	if err == nil {
		t.Error("getCharactersForUser() should return error")
	}
}

func TestGetLastCID(t *testing.T) {
	userRepo := &mockSignUserRepo{
		lastCharacter: 12345,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	lastCID := server.getLastCID(1)
	if lastCID != 12345 {
		t.Errorf("getLastCID() = %d, want 12345", lastCID)
	}
}

func TestGetLastCIDNoResult(t *testing.T) {
	userRepo := &mockSignUserRepo{
		lastCharacterErr: sql.ErrNoRows,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	lastCID := server.getLastCID(1)
	if lastCID != 0 {
		t.Errorf("getLastCID() with no result = %d, want 0", lastCID)
	}
}

func TestGetUserRights(t *testing.T) {
	userRepo := &mockSignUserRepo{
		rights: 30,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	rights := server.getUserRights(1)
	if rights == 0 {
		t.Error("getUserRights() should return non-zero value")
	}
}

func TestGetReturnExpiry(t *testing.T) {
	recentLogin := time.Now().Add(-time.Hour * 24)
	userRepo := &mockSignUserRepo{
		lastLogin:    recentLogin,
		returnExpiry: time.Now().Add(time.Hour * 24 * 30),
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	expiry := server.getReturnExpiry(1)
	if expiry.Before(time.Now()) {
		t.Error("getReturnExpiry() should return future date")
	}
	if !userRepo.updateLastLoginCalled {
		t.Error("getReturnExpiry() should update last login")
	}
}

func TestGetReturnExpiryInactiveUser(t *testing.T) {
	oldLogin := time.Now().Add(-time.Hour * 24 * 100)
	userRepo := &mockSignUserRepo{
		lastLogin: oldLogin,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	expiry := server.getReturnExpiry(1)
	if expiry.Before(time.Now()) {
		t.Error("getReturnExpiry() should return future date for inactive user")
	}
	if !userRepo.updateReturnExpiryCalled {
		t.Error("getReturnExpiry() should update return expiry for inactive user")
	}
	if !userRepo.updateLastLoginCalled {
		t.Error("getReturnExpiry() should update last login")
	}
}

func TestGetReturnExpiryDBError(t *testing.T) {
	recentLogin := time.Now().Add(-time.Hour * 24)
	userRepo := &mockSignUserRepo{
		lastLogin:       recentLogin,
		returnExpiryErr: sql.ErrNoRows,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	expiry := server.getReturnExpiry(1)
	if expiry.IsZero() {
		t.Error("getReturnExpiry() should return non-zero time even on error")
	}
	if !userRepo.updateReturnExpiryCalled {
		t.Error("getReturnExpiry() should update return expiry on fallback")
	}
}

func TestNewUserChara(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		newCharCount: 0,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	err := server.newUserChara(1)
	if err != nil {
		t.Errorf("newUserChara() error: %v", err)
	}
	if !charRepo.createCalled {
		t.Error("newUserChara() should call CreateCharacter")
	}
}

func TestNewUserCharaAlreadyHasNewChar(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		newCharCount: 1,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	err := server.newUserChara(1)
	if err != nil {
		t.Errorf("newUserChara() should return nil when user already has new char: %v", err)
	}
	if charRepo.createCalled {
		t.Error("newUserChara() should not call CreateCharacter when user already has new char")
	}
}

func TestNewUserCharaCountError(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		newCharCountErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	err := server.newUserChara(1)
	if err == nil {
		t.Error("newUserChara() should return error when count query fails")
	}
}

func TestNewUserCharaInsertError(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		newCharCount: 0,
		createErr:    sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	err := server.newUserChara(1)
	if err == nil {
		t.Error("newUserChara() should return error when insert fails")
	}
}

func TestRegisterDBAccount(t *testing.T) {
	userRepo := &mockSignUserRepo{
		registerUID: 1,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	uid, err := server.registerDBAccount("newuser", "password123")
	if err != nil {
		t.Errorf("registerDBAccount() error: %v", err)
	}
	if uid != 1 {
		t.Errorf("registerDBAccount() uid = %d, want 1", uid)
	}
	if !userRepo.registered {
		t.Error("registerDBAccount() should call Register")
	}
}

func TestRegisterDBAccountDuplicateUser(t *testing.T) {
	userRepo := &mockSignUserRepo{
		registerErr: sql.ErrNoRows,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	_, err := server.registerDBAccount("existinguser", "password123")
	if err == nil {
		t.Error("registerDBAccount() should return error for duplicate user")
	}
}

func TestDeleteCharacter(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: true,
	}
	charRepo := &mockSignCharacterRepo{
		isNew: false,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
		charRepo:    charRepo,
	}

	err := server.deleteCharacter(123, "validtoken", 0)
	if err != nil {
		t.Errorf("deleteCharacter() error: %v", err)
	}
	if !charRepo.softDeleteCalled {
		t.Error("deleteCharacter() should soft delete existing character")
	}
}

func TestDeleteNewCharacter(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: true,
	}
	charRepo := &mockSignCharacterRepo{
		isNew: true,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
		charRepo:    charRepo,
	}

	err := server.deleteCharacter(123, "validtoken", 0)
	if err != nil {
		t.Errorf("deleteCharacter() error: %v", err)
	}
	if !charRepo.hardDeleteCalled {
		t.Error("deleteCharacter() should hard delete new character")
	}
}

func TestDeleteCharacterInvalidToken(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: false,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	err := server.deleteCharacter(123, "invalidtoken", 0)
	if err == nil {
		t.Error("deleteCharacter() should return error for invalid token")
	}
}

func TestDeleteCharacterDeleteError(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: true,
	}
	charRepo := &mockSignCharacterRepo{
		isNew:         false,
		softDeleteErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
		charRepo:    charRepo,
	}

	err := server.deleteCharacter(123, "validtoken", 0)
	if err == nil {
		t.Error("deleteCharacter() should return error when update fails")
	}
}

func TestGetFriendsForCharactersEmpty(t *testing.T) {
	charRepo := &mockSignCharacterRepo{}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{}
	friends := server.getFriendsForCharacters(chars)
	if len(friends) != 0 {
		t.Errorf("getFriendsForCharacters() for empty chars = %d, want 0", len(friends))
	}
}

func TestGetGuildmatesForCharactersEmpty(t *testing.T) {
	charRepo := &mockSignCharacterRepo{}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{}
	guildmates := server.getGuildmatesForCharacters(chars)
	if len(guildmates) != 0 {
		t.Errorf("getGuildmatesForCharacters() for empty chars = %d, want 0", len(guildmates))
	}
}

func TestGetFriendsForCharacters(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		friends: []members{
			{ID: 2, Name: "Friend1"},
			{ID: 3, Name: "Friend2"},
		},
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{
		{ID: 1, Name: "Hunter1"},
	}

	friends := server.getFriendsForCharacters(chars)
	if len(friends) != 2 {
		t.Errorf("getFriendsForCharacters() = %d, want 2", len(friends))
	}
	if friends[0].CID != 1 {
		t.Errorf("friends[0].CID = %d, want 1", friends[0].CID)
	}
}

func TestGetGuildmatesForCharacters(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		guildmates: []members{
			{ID: 2, Name: "Guildmate1"},
			{ID: 3, Name: "Guildmate2"},
		},
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{
		{ID: 1, Name: "Hunter1"},
	}

	guildmates := server.getGuildmatesForCharacters(chars)
	if len(guildmates) != 2 {
		t.Errorf("getGuildmatesForCharacters() = %d, want 2", len(guildmates))
	}
	if guildmates[0].CID != 1 {
		t.Errorf("guildmates[0].CID = %d, want 1", guildmates[0].CID)
	}
}

func TestGetGuildmatesNotInGuild(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		guildmates: nil,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{
		{ID: 1, Name: "Hunter1"},
	}

	guildmates := server.getGuildmatesForCharacters(chars)
	if len(guildmates) != 0 {
		t.Errorf("getGuildmatesForCharacters() for non-guild member = %d, want 0", len(guildmates))
	}
}

func TestValidateLoginSuccess(t *testing.T) {
	// bcrypt hash for "password123"
	hash := "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
	userRepo := &mockSignUserRepo{
		credUID:      1,
		credPassword: hash,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	// Note: bcrypt verification will fail with this test hash since it's not a real hash of "password123"
	// The important thing is testing the flow, not actual bcrypt verification
	_, resp := server.validateLogin("testuser", "password123")
	// This will return SIGN_EPASS since the hash doesn't match, which is expected behavior
	if resp == SIGN_EABORT {
		t.Error("validateLogin() should not abort for valid credentials lookup")
	}
}

func TestValidateLoginUserNotFound(t *testing.T) {
	userRepo := &mockSignUserRepo{
		credErr: sql.ErrNoRows,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	_, resp := server.validateLogin("unknown", "password")
	if resp != SIGN_EAUTH {
		t.Errorf("validateLogin() for unknown user = %d, want SIGN_EAUTH(%d)", resp, SIGN_EAUTH)
	}
}

func TestValidateLoginAutoCreate(t *testing.T) {
	userRepo := &mockSignUserRepo{
		credErr:     sql.ErrNoRows,
		registerUID: 42,
	}

	server := &Server{
		logger: zap.NewNop(),
		erupeConfig: &cfg.Config{
			AutoCreateAccount: true,
		},
		userRepo: userRepo,
	}

	uid, resp := server.validateLogin("newuser", "password")
	if resp != SIGN_SUCCESS {
		t.Errorf("validateLogin() with auto-create = %d, want SIGN_SUCCESS(%d)", resp, SIGN_SUCCESS)
	}
	if uid != 42 {
		t.Errorf("validateLogin() uid = %d, want 42", uid)
	}
}

func TestValidateLoginDBError(t *testing.T) {
	userRepo := &mockSignUserRepo{
		credErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	_, resp := server.validateLogin("testuser", "password")
	if resp != SIGN_EABORT {
		t.Errorf("validateLogin() on DB error = %d, want SIGN_EABORT(%d)", resp, SIGN_EABORT)
	}
}

func TestValidateTokenValid(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: true,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	if !server.validateToken("validtoken", 0) {
		t.Error("validateToken() should return true for valid token")
	}
}

func TestValidateTokenInvalid(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateResult: false,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	if server.validateToken("invalidtoken", 0) {
		t.Error("validateToken() should return false for invalid token")
	}
}

func TestValidateTokenDBError(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		validateErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	if server.validateToken("token", 0) {
		t.Error("validateToken() should return false on DB error")
	}
}

func TestGetUserRightsZeroUID(t *testing.T) {
	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
	}

	rights := server.getUserRights(0)
	if rights != 0 {
		t.Errorf("getUserRights(0) = %d, want 0", rights)
	}
}

func TestRegisterPsnToken(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		registerPSNTokenID: 42,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	tid, tok, err := server.registerPsnToken("test_psn")
	if err != nil {
		t.Errorf("registerPsnToken() error: %v", err)
	}
	if tid != 42 {
		t.Errorf("registerPsnToken() tokenID = %d, want 42", tid)
	}
	if tok == "" {
		t.Error("registerPsnToken() token should not be empty")
	}
}

func TestRegisterPsnToken_Error(t *testing.T) {
	sessionRepo := &mockSignSessionRepo{
		registerPSNErr: errMockDB,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		sessionRepo: sessionRepo,
	}

	_, _, err := server.registerPsnToken("test_psn")
	if err == nil {
		t.Error("registerPsnToken() should return error")
	}
}

func TestGetUserRightsDBError(t *testing.T) {
	userRepo := &mockSignUserRepo{
		rightsErr: sql.ErrConnDone,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	rights := server.getUserRights(1)
	if rights != 0 {
		t.Errorf("getUserRights() on error = %d, want 0", rights)
	}
}

func TestGetFriendsForCharactersError(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		getFriendsErr: errMockDB,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{{ID: 1, Name: "Hunter1"}}
	friends := server.getFriendsForCharacters(chars)
	if len(friends) != 0 {
		t.Errorf("getFriendsForCharacters() on error = %d, want 0", len(friends))
	}
}

func TestValidateLogin_CorrectPassword(t *testing.T) {
	password := "correctpassword"
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
	if err != nil {
		t.Fatal("failed to hash password:", err)
	}

	userRepo := &mockSignUserRepo{
		credUID:      1,
		credPassword: string(hash),
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	uid, resp := server.validateLogin("testuser", password)
	if resp != SIGN_SUCCESS {
		t.Errorf("validateLogin() correct password = %d, want SIGN_SUCCESS(%d)", resp, SIGN_SUCCESS)
	}
	if uid != 1 {
		t.Errorf("validateLogin() uid = %d, want 1", uid)
	}
}

func TestValidateLogin_PermanentBan(t *testing.T) {
	password := "password"
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
	if err != nil {
		t.Fatal("failed to hash password:", err)
	}

	userRepo := &mockSignUserRepo{
		credUID:       1,
		credPassword:  string(hash),
		permanentBans: 1,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	uid, resp := server.validateLogin("banned", password)
	if resp != SIGN_EELIMINATE {
		t.Errorf("validateLogin() permanent ban = %d, want SIGN_EELIMINATE(%d)", resp, SIGN_EELIMINATE)
	}
	if uid != 1 {
		t.Errorf("validateLogin() uid = %d, want 1", uid)
	}
}

func TestValidateLogin_ActiveBan(t *testing.T) {
	password := "password"
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
	if err != nil {
		t.Fatal("failed to hash password:", err)
	}

	userRepo := &mockSignUserRepo{
		credUID:      1,
		credPassword: string(hash),
		activeBans:   1,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		userRepo:    userRepo,
	}

	_, resp := server.validateLogin("suspended", password)
	if resp != SIGN_ESUSPEND {
		t.Errorf("validateLogin() active ban = %d, want SIGN_ESUSPEND(%d)", resp, SIGN_ESUSPEND)
	}
}

func TestValidateLogin_AutoCreateError(t *testing.T) {
	userRepo := &mockSignUserRepo{
		credErr:     sql.ErrNoRows,
		registerErr: errMockDB,
	}

	server := &Server{
		logger: zap.NewNop(),
		erupeConfig: &cfg.Config{
			AutoCreateAccount: true,
		},
		userRepo: userRepo,
	}

	_, resp := server.validateLogin("newuser", "password")
	if resp != SIGN_EABORT {
		t.Errorf("validateLogin() auto-create error = %d, want SIGN_EABORT(%d)", resp, SIGN_EABORT)
	}
}

func TestGetGuildmatesForCharactersError(t *testing.T) {
	charRepo := &mockSignCharacterRepo{
		getGuildmatesErr: errMockDB,
	}

	server := &Server{
		logger:      zap.NewNop(),
		erupeConfig: &cfg.Config{},
		charRepo:    charRepo,
	}

	chars := []character{{ID: 1, Name: "Hunter1"}}
	guildmates := server.getGuildmatesForCharacters(chars)
	if len(guildmates) != 0 {
		t.Errorf("getGuildmatesForCharacters() on error = %d, want 0", len(guildmates))
	}
}
