package auth

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"tercul/internal/domain"
	"tercul/internal/platform/auth"
	"tercul/internal/platform/log"
	"time"

	"github.com/asaskevich/govalidator"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
)

var (
	ErrInvalidCredentials = errors.New("invalid credentials")
	ErrUserAlreadyExists  = errors.New("user already exists")
	ErrInvalidInput       = errors.New("invalid input")
)

// LoginInput represents login request data
type LoginInput struct {
	Email    string `json:"email" validate:"required,email"`
	Password string `json:"password" validate:"required,min=6"`
}

// RegisterInput represents registration request data
type RegisterInput struct {
	Username  string `json:"username" validate:"required,min=3,max=50"`
	Email     string `json:"email" validate:"required,email"`
	Password  string `json:"password" validate:"required,min=6"`
	FirstName string `json:"first_name" validate:"required,min=1,max=50"`
	LastName  string `json:"last_name" validate:"required,min=1,max=50"`
}

// AuthResponse represents authentication response
type AuthResponse struct {
	Token     string        `json:"token"`
	User      *domain.User `json:"user"`
	ExpiresAt time.Time     `json:"expires_at"`
}

// AuthCommands contains the command handlers for authentication.
type AuthCommands struct {
	userRepo   domain.UserRepository
	jwtManager auth.JWTManagement
	tracer     trace.Tracer
}

// NewAuthCommands creates a new AuthCommands handler.
func NewAuthCommands(userRepo domain.UserRepository, jwtManager auth.JWTManagement) *AuthCommands {
	return &AuthCommands{
		userRepo:   userRepo,
		jwtManager: jwtManager,
		tracer:     otel.Tracer("auth.commands"),
	}
}

// Login authenticates a user and returns a JWT token
func (c *AuthCommands) Login(ctx context.Context, input LoginInput) (*AuthResponse, error) {
	ctx, span := c.tracer.Start(ctx, "Login")
	defer span.End()
	logger := log.FromContext(ctx).With("email", input.Email)

	if err := validateLoginInput(input); err != nil {
		logger.Warn("Login validation failed")
		return nil, fmt.Errorf("%w: %v", ErrInvalidInput, err)
	}

	email := strings.TrimSpace(input.Email)
	logger.Debug("Attempting to log in user")
	user, err := c.userRepo.FindByEmail(ctx, email)
	if err != nil {
		logger.Warn("Login failed - user not found")
		return nil, ErrInvalidCredentials
	}

	logger = logger.With("user_id", user.ID)

	if !user.Active {
		logger.Warn("Login failed - user inactive")
		return nil, ErrInvalidCredentials
	}

	if !user.CheckPassword(input.Password) {
		logger.Warn("Login failed - invalid password")
		return nil, ErrInvalidCredentials
	}

	token, err := c.jwtManager.GenerateToken(user)
	if err != nil {
		logger.Error(err, "Failed to generate JWT token")
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	now := time.Now()
	user.LastLoginAt = &now
	if err := c.userRepo.Update(ctx, user); err != nil {
		logger.Error(err, "Failed to update last login time")
		// Do not fail the login if this update fails
	}

	logger.Info("User logged in successfully")
	return &AuthResponse{
		Token:     token,
		User:      user,
		ExpiresAt: time.Now().Add(24 * time.Hour), // This should be configurable
	}, nil
}

// Logout invalidates a user's session.
func (c *AuthCommands) Logout(ctx context.Context) error {
	// Implementation depends on how sessions are managed (e.g., blacklisting tokens).
	// For now, this is a placeholder.
	return nil
}

// RefreshToken generates a new token for an authenticated user.
func (c *AuthCommands) RefreshToken(ctx context.Context) (*AuthResponse, error) {
	userID, ok := auth.GetUserIDFromContext(ctx)
	if !ok {
		return nil, domain.ErrUnauthorized
	}

	user, err := c.userRepo.GetByID(ctx, userID)
	if err != nil {
		return nil, domain.ErrUserNotFound
	}

	token, err := c.jwtManager.GenerateToken(user)
	if err != nil {
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	return &AuthResponse{
		Token:     token,
		User:      user,
		ExpiresAt: time.Now().Add(24 * time.Hour),
	}, nil
}

// ForgotPassword initiates the password reset process for a user.
func (c *AuthCommands) ForgotPassword(ctx context.Context, email string) error {
	// In a real application, this would generate a reset token and send an email.
	// For now, this is a placeholder.
	return nil
}

// ResetPasswordInput represents the input for resetting a password.
type ResetPasswordInput struct {
	Token       string `json:"token"`
	NewPassword string `json:"new_password"`
}

// ResetPassword resets a user's password using a reset token.
func (c *AuthCommands) ResetPassword(ctx context.Context, input ResetPasswordInput) error {
	// In a real application, this would validate the token, find the user, and update the password.
	// For now, this is a placeholder.
	return nil
}

// VerifyEmail verifies a user's email address using a verification token.
func (c *AuthCommands) VerifyEmail(ctx context.Context, token string) error {
	// In a real application, this would validate the token and mark the user's email as verified.
	// For now, this is a placeholder.
	return nil
}

// ResendVerificationEmail resends the email verification link to a user.
func (c *AuthCommands) ResendVerificationEmail(ctx context.Context, email string) error {
	// In a real application, this would generate a new verification token and send it.
	// For now, this is a placeholder.
	return nil
}

// ChangePasswordInput represents the input for changing a password.
type ChangePasswordInput struct {
	UserID          uint
	CurrentPassword string
	NewPassword     string
}

// ChangePassword allows an authenticated user to change their password.
func (c *AuthCommands) ChangePassword(ctx context.Context, input ChangePasswordInput) error {
	user, err := c.userRepo.GetByID(ctx, input.UserID)
	if err != nil {
		return domain.ErrUserNotFound
	}

	if !user.CheckPassword(input.CurrentPassword) {
		return ErrInvalidCredentials
	}

	if err := user.SetPassword(input.NewPassword); err != nil {
		return err
	}

	return c.userRepo.Update(ctx, user)
}

// Register creates a new user account
func (c *AuthCommands) Register(ctx context.Context, input RegisterInput) (*AuthResponse, error) {
	ctx, span := c.tracer.Start(ctx, "Register")
	defer span.End()
	logger := log.FromContext(ctx).With("email", input.Email).With("username", input.Username)

	if err := validateRegisterInput(input); err != nil {
		logger.Warn("Registration validation failed")
		return nil, fmt.Errorf("%w: %v", ErrInvalidInput, err)
	}

	email := strings.TrimSpace(input.Email)
	username := strings.TrimSpace(input.Username)
	logger.Debug("Attempting to register new user")

	existingUser, _ := c.userRepo.FindByEmail(ctx, email)
	if existingUser != nil {
		logger.Warn("Registration failed - email already exists")
		return nil, ErrUserAlreadyExists
	}

	existingUser, _ = c.userRepo.FindByUsername(ctx, username)
	if existingUser != nil {
		logger.Warn("Registration failed - username already exists")
		return nil, ErrUserAlreadyExists
	}

	user := &domain.User{
		Username:    username,
		Email:       email,
		Password:    input.Password,
		FirstName:   strings.TrimSpace(input.FirstName),
		LastName:    strings.TrimSpace(input.LastName),
		DisplayName: fmt.Sprintf("%s %s", strings.TrimSpace(input.FirstName), strings.TrimSpace(input.LastName)),
		Role:        domain.UserRoleReader,
		Active:      true,
		Verified:    false, // Should be false until email verification
	}

	if err := c.userRepo.Create(ctx, user); err != nil {
		logger.Error(err, "Failed to create user")
		return nil, fmt.Errorf("failed to create user: %w", err)
	}

	logger = logger.With("user_id", user.ID)

	token, err := c.jwtManager.GenerateToken(user)
	if err != nil {
		logger.Error(err, "Failed to generate JWT token for new user")
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	logger.Info("User registered successfully")
	return &AuthResponse{
		Token:     token,
		User:      user,
		ExpiresAt: time.Now().Add(24 * time.Hour), // This should be configurable
	}, nil
}

func validateLoginInput(input LoginInput) error {
	if input.Email == "" {
		return errors.New("email is required")
	}
	if !govalidator.IsEmail(strings.TrimSpace(input.Email)) {
		return errors.New("invalid email format")
	}
	if len(input.Password) < 6 {
		return errors.New("password must be at least 6 characters")
	}
	return nil
}

func validateRegisterInput(input RegisterInput) error {
	if !govalidator.IsEmail(strings.TrimSpace(input.Email)) {
		return errors.New("invalid email format")
	}
	if len(input.Password) < 6 {
		return errors.New("password must be at least 6 characters")
	}
	username := strings.TrimSpace(input.Username)
	if len(username) < 3 || len(username) > 50 {
		return errors.New("username must be between 3 and 50 characters")
	}
	if !govalidator.Matches(username, `^[a-zA-Z0-9_-]+$`) {
		return errors.New("username can only contain letters, numbers, underscores, and hyphens")
	}
	return nil
}
