package auth

import (
	"context"
	"errors"
	"time"

	"catalogue.midpdsa.com/src/db/catalogue"
	"github.com/golang-jwt/jwt/v5"
	"github.com/jackc/pgx/v5"
	"golang.org/x/crypto/bcrypt"
)

var jwtKey = []byte("your_secret_key") // In production, use an environment variable

type Claims struct {
	UserID int32 `json:"user_id"`
	jwt.RegisteredClaims
}

type AuthService struct {
	queries *catalogue.Queries
}

func NewAuthService(queries *catalogue.Queries) *AuthService {
	return &AuthService{queries: queries}
}

func (s *AuthService) Login(ctx context.Context, email, password string) (string, error) {
	user, err := s.queries.GetUserByEmail(ctx, email)
	if err != nil {
		return "", errors.New("invalid email or password")
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if err != nil {
		return "", errors.New("invalid email or password")
	}

	expirationTime := time.Now().Add(24 * time.Hour)
	claims := &Claims{
		UserID: user.ID,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expirationTime),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtKey)
}

func (s *AuthService) Register(ctx context.Context, email, password string) error {
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	_, err = s.queries.CreateUser(ctx, catalogue.CreateUserParams{
		Email:    email,
		Password: string(hashedPassword),
	})
	return err
}

// EnsureUser creates the user with the given credentials only if it does not
// already exist. It is used to seed a default admin on startup.
func (s *AuthService) EnsureUser(ctx context.Context, email, password string) error {
	if _, err := s.queries.GetUserByEmail(ctx, email); err == nil {
		return nil
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return err
	}
	return s.Register(ctx, email, password)
}

func (s *AuthService) UpdatePassword(ctx context.Context, email, newPassword string) error {
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	return s.queries.UpdateUserPassword(ctx, catalogue.UpdateUserPasswordParams{
		Email:    email,
		Password: string(hashedPassword),
	})
}

// ChangePassword verifies the current password before updating to a new one.
// It returns the same generic error for an unknown email or a wrong password
// to avoid user enumeration.
func (s *AuthService) ChangePassword(ctx context.Context, email, currentPassword, newPassword string) error {
	user, err := s.queries.GetUserByEmail(ctx, email)
	if err != nil {
		return errors.New("invalid email or password")
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword)); err != nil {
		return errors.New("invalid email or password")
	}

	return s.UpdatePassword(ctx, email, newPassword)
}
