package auth

import (
	"context"
	"errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"tercul/internal/domain"
	"testing"
)


type AuthCommandsSuite struct {
	suite.Suite
	userRepo   *mockUserRepository
	jwtManager *mockJWTManager
	commands   *AuthCommands
}

func (s *AuthCommandsSuite) SetupTest() {
	s.userRepo = newMockUserRepository()
	s.jwtManager = &mockJWTManager{}
	s.commands = NewAuthCommands(s.userRepo, s.jwtManager)
}

func TestAuthCommandsSuite(t *testing.T) {
	suite.Run(t, new(AuthCommandsSuite))
}

func (s *AuthCommandsSuite) TestLogin_Success() {
	user := domain.User{
		Email:    "test@example.com",
		Password: "password",
		Active:   true,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	input := LoginInput{Email: "test@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)

	assert.NoError(s.T(), err)
	assert.NotNil(s.T(), resp)
	assert.Equal(s.T(), "test-token", resp.Token)
	assert.Equal(s.T(), user.ID, resp.User.ID)
}

func (s *AuthCommandsSuite) TestLogin_InvalidInput() {
	input := LoginInput{Email: "invalid-email", Password: "short"}
	resp, err := s.commands.Login(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestValidateLoginInput_EmptyEmail() {
	input := LoginInput{Email: "", Password: "password"}
	err := validateLoginInput(input)
	assert.Error(s.T(), err)
}

func (s *AuthCommandsSuite) TestValidateLoginInput_ShortPassword() {
	input := LoginInput{Email: "test@example.com", Password: "short"}
	err := validateLoginInput(input)
	assert.Error(s.T(), err)
}

func (s *AuthCommandsSuite) TestValidateRegisterInput_ShortPassword() {
	input := RegisterInput{Email: "test@example.com", Password: "short"}
	err := validateRegisterInput(input)
	assert.Error(s.T(), err)
}

func (s *AuthCommandsSuite) TestValidateRegisterInput_ShortUsername() {
	input := RegisterInput{Username: "a", Email: "test@example.com", Password: "password"}
	err := validateRegisterInput(input)
	assert.Error(s.T(), err)
}

func (s *AuthCommandsSuite) TestValidateRegisterInput_LongUsername() {
	input := RegisterInput{Username: "a51characterusernameisdefinitelytoolongforthisvalidation", Email: "test@example.com", Password: "password"}
	err := validateRegisterInput(input)
	assert.Error(s.T(), err)
}

func (s *AuthCommandsSuite) TestLogin_SuccessUpdate() {
	user := domain.User{
		Email:    "test@example.com",
		Password: "password",
		Active:   true,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))
	s.userRepo.updateFunc = func(ctx context.Context, user *domain.User) error {
		return nil
	}

	input := LoginInput{Email: "test@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)

	assert.NoError(s.T(), err)
	assert.NotNil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_InvalidEmail() {
	input := RegisterInput{
		Username:  "newuser",
		Email:     "invalid-email",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestLogin_UpdateUserError() {
	user := domain.User{
		Email:    "test@example.com",
		Password: "password",
		Active:   true,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))
	s.userRepo.updateFunc = func(ctx context.Context, user *domain.User) error {
		return errors.New("update error")
	}

	input := LoginInput{Email: "test@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)

	assert.NoError(s.T(), err)
	assert.NotNil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_InvalidUsername() {
	input := RegisterInput{
		Username:  "invalid username",
		Email:     "new@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestLogin_UserNotFound() {
	input := LoginInput{Email: "notfound@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)
	assert.ErrorIs(s.T(), err, ErrInvalidCredentials)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestLogin_InactiveUser() {
	user := domain.User{
		Email:    "inactive@example.com",
		Password: "password",
		Active:   false,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	input := LoginInput{Email: "inactive@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)
	assert.ErrorIs(s.T(), err, ErrInvalidCredentials)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestLogin_InvalidPassword() {
	user := domain.User{
		Email:    "test@example.com",
		Password: "password",
		Active:   true,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	input := LoginInput{Email: "test@example.com", Password: "wrong-password"}
	resp, err := s.commands.Login(context.Background(), input)
	assert.ErrorIs(s.T(), err, ErrInvalidCredentials)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestLogin_TokenGenerationError() {
	user := domain.User{
		Email:    "test@example.com",
		Password: "password",
		Active:   true,
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	s.jwtManager.generateTokenFunc = func(user *domain.User) (string, error) {
		return "", errors.New("jwt error")
	}
	input := LoginInput{Email: "test@example.com", Password: "password"}
	resp, err := s.commands.Login(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_Success() {
	input := RegisterInput{
		Username:  "newuser",
		Email:     "new@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.NoError(s.T(), err)
	assert.NotNil(s.T(), resp)
	assert.Equal(s.T(), "test-token", resp.Token)
	assert.Equal(s.T(), "newuser", resp.User.Username)
}

func (s *AuthCommandsSuite) TestRegister_InvalidInput() {
	input := RegisterInput{Email: "invalid"}
	resp, err := s.commands.Register(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_EmailExists() {
	user := domain.User{
		Email: "exists@example.com",
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	input := RegisterInput{
		Username:  "newuser",
		Email:     "exists@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.ErrorIs(s.T(), err, ErrUserAlreadyExists)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_UsernameExists() {
	user := domain.User{
		Username: "exists",
	}
	assert.NoError(s.T(), s.userRepo.Create(context.Background(), &user))

	input := RegisterInput{
		Username:  "exists",
		Email:     "new@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.ErrorIs(s.T(), err, ErrUserAlreadyExists)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_CreateUserError() {
	s.userRepo.createFunc = func(ctx context.Context, user *domain.User) error {
		return errors.New("db error")
	}
	input := RegisterInput{
		Username:  "newuser",
		Email:     "new@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}

func (s *AuthCommandsSuite) TestRegister_TokenGenerationError() {
	s.jwtManager.generateTokenFunc = func(user *domain.User) (string, error) {
		return "", errors.New("jwt error")
	}
	input := RegisterInput{
		Username:  "newuser",
		Email:     "new@example.com",
		Password:  "password",
		FirstName: "New",
		LastName:  "User",
	}
	resp, err := s.commands.Register(context.Background(), input)
	assert.Error(s.T(), err)
	assert.Nil(s.T(), resp)
}
