package repository

import (
	"actatempus_backend/internal/domain/app_error"
	"actatempus_backend/internal/domain/repository"
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	E "github.com/IBM/fp-go/either"
	"github.com/golang-jwt/jwt/v5"
)

type InMemoryAuthRepository struct {
	secretKey    string
	sessionCache map[string]string
	mu           sync.RWMutex
}

func NewInMemoryAuthRepositoryImpl(secretKey string) repository.AuthRepository {
	return &InMemoryAuthRepository{
		secretKey:    secretKey,
		sessionCache: make(map[string]string),
		mu:           sync.RWMutex{},
	}
}

func (r *InMemoryAuthRepository) GenerateToken(ctx context.Context) func(userID string) E.Either[error, string] {
	return func(userID string) E.Either[error, string] {
		token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
			"userID": userID,
			"exp":    time.Now().Add(24 * time.Hour).Unix(),
		}).SignedString([]byte(r.secretKey))

		// add token to cache
		r.mu.Lock()
		defer r.mu.Unlock() // unlock after function returns
		r.sessionCache[token] = userID

		return E.TryCatch(
			token,
			err,
			func(e error) error {
				return app_error.NewInternalError(fmt.Errorf("could not generate token: %w", e))
			},
		)
	}
}

func (r *InMemoryAuthRepository) ValidateToken(ctx context.Context) func(token string) E.Either[error, string] {
	return func(token string) E.Either[error, string] {
		// Separate value and error generation for TryCatch
		userID, err := func() (string, error) {
			parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, errors.New("invalid signing method")
				}
				return []byte(r.secretKey), nil
			})
			if err != nil {
				return "", err
			}

			if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok && parsedToken.Valid {
				userID, ok := claims["userID"].(string)
				if !ok {
					return "", app_error.NewAuthError(fmt.Sprintf("invalid token claims"))
				}

				// Check the cache
				r.mu.RLock()
				defer r.mu.RUnlock()
				if cachedUserID, exists := r.sessionCache[token]; exists && cachedUserID == userID {
					return userID, nil
				}
				// print chache to see if it is working
				fmt.Println(r.sessionCache)
				return "", app_error.NewAuthError(fmt.Sprintf("token not found in cache"))
			}

			return "", app_error.NewAuthError(fmt.Sprintf("invalid token"))
		}()

		// Use TryCatch with separated value and error
		return E.TryCatch(
			userID,
			err,
			func(e error) error {
				return e
			},
		)
	}
}

func (r *InMemoryAuthRepository) RevokeToken(ctx context.Context) func(token string) E.Either[error, string] {
	return func(token string) E.Either[error, string] {
		// Separate value and error generation for TryCatch
		userID, err := func() (string, error) {
			r.mu.Lock()
			defer r.mu.Unlock()

			if userID, exists := r.sessionCache[token]; exists {
				delete(r.sessionCache, token)
				return userID, nil
			}

			return "", app_error.NewAuthError(fmt.Sprintf("token not found"))
		}()

		// Use TryCatch with separated value and error
		return E.TryCatch(
			userID,
			err,
			func(e error) error {
				return e
			},
		)
	}

}
