package authentication

import (
	"context"
	"crypto/rand"
	"encoding/base32"
	"errors"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"golang.org/x/crypto/bcrypt"
)

var (
	ErrInvalidPassword      = errors.New("incorrect password")
	ErrUserNotFound         = errors.New("user not found")
	ErrExpiredToken         = errors.New("token expired")
	ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again
	ErrUserSuspended        = errors.New("user has been suspended")
)

type LoginSuccess struct {
	UserId    int64
	Role      domain.Role
	RfToken   string
	CompanyID domain.ValidInt64
}

func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) {
	user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone)
	if err != nil {
		return LoginSuccess{}, err
	}
	err = matchPassword(password, user.Password)
	if err != nil {
		return LoginSuccess{}, err
	}
	if user.Suspended {
		return LoginSuccess{}, ErrUserSuspended
	}

	oldRefreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)

	if err != nil && err != ErrRefreshTokenNotFound {
		return LoginSuccess{}, err
	}

	// If old refresh token is not revoked, revoke it
	if err == nil && !oldRefreshToken.Revoked {
		err = s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token)
		if err != nil {
			return LoginSuccess{}, err
		}
	}

	refreshToken, err := generateRefreshToken()
	if err != nil {
		return LoginSuccess{}, err
	}
	err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
		Token:     refreshToken,
		UserID:    user.ID,
		CreatedAt: time.Now(),
		ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
	})

	if err != nil {
		return LoginSuccess{}, err
	}
	return LoginSuccess{
		UserId:    user.ID,
		Role:      user.Role,
		RfToken:   refreshToken,
		CompanyID: user.CompanyID,
	}, nil
}

func (s *Service) RefreshToken(ctx context.Context, refToken string) (domain.RefreshToken, error) {

	token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
	if err != nil {
		return domain.RefreshToken{}, err
	}
	if token.Revoked {
		return domain.RefreshToken{}, ErrRefreshTokenNotFound
	}
	if token.ExpiresAt.Before(time.Now()) {
		return domain.RefreshToken{}, ErrExpiredToken
	}

	// newRefToken, err := generateRefreshToken()
	// if err != nil {
	// 	return "", err
	// }

	// err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
	// 	Token:     newRefToken,
	// 	UserID:    token.UserID,
	// 	CreatedAt: time.Now(),
	// 	ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
	// })
	return token, nil
}

func (s *Service) GetLastLogin(ctx context.Context, user_id int64) (*time.Time, error) {
	refreshToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user_id)
	if err != nil {
		return nil, err
	}

	return &refreshToken.CreatedAt, nil

}

func (s *Service) Logout(ctx context.Context, refToken string) error {
	token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
	if err != nil {
		return err
	}
	if token.Revoked {
		return ErrRefreshTokenNotFound
	}
	if token.ExpiresAt.Before(time.Now()) {
		return ErrExpiredToken
	}

	return s.tokenStore.RevokeRefreshToken(ctx, refToken)
}

func matchPassword(plaintextPassword string, hash []byte) error {
	err := bcrypt.CompareHashAndPassword(hash, []byte(plaintextPassword))
	if err != nil {
		switch {
		case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
			return ErrInvalidPassword
		default:
			return err
		}
	}

	return err
}
func generateRefreshToken() (string, error) {
	randomBytes := make([]byte, 32)
	_, err := rand.Read(randomBytes)
	if err != nil {
		return "", err
	}
	plaintext := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(randomBytes)
	return plaintext, nil
}
