package auth

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/tdvorak/seen/backend/internal/config"
	"github.com/tdvorak/seen/backend/internal/domain"
	"github.com/tdvorak/seen/backend/internal/repositories/postgres"
	"go.uber.org/zap"
)

type inMemoryRepo struct {
	usersByEmail map[string]domain.User
	sessions     map[string]domain.Session
}

func newInMemoryRepo() *inMemoryRepo {
	return &inMemoryRepo{
		usersByEmail: make(map[string]domain.User),
		sessions:     make(map[string]domain.Session),
	}
}

func (r *inMemoryRepo) CreateUser(_ context.Context, user domain.User) error {
	if _, exists := r.usersByEmail[user.Email]; exists {
		return postgres.ErrUserAlreadyExists
	}
	r.usersByEmail[user.Email] = user
	return nil
}

func (r *inMemoryRepo) FindUserByEmail(_ context.Context, email string) (*domain.User, error) {
	user, exists := r.usersByEmail[email]
	if !exists {
		return nil, nil
	}
	copy := user
	return &copy, nil
}

func (r *inMemoryRepo) FindUserByID(_ context.Context, userID uuid.UUID) (*domain.User, error) {
	for _, user := range r.usersByEmail {
		if user.ID == userID {
			copy := user
			return &copy, nil
		}
	}

	return nil, nil
}

func (r *inMemoryRepo) CreateSession(_ context.Context, session domain.Session) error {
	r.sessions[session.RefreshToken] = session
	return nil
}

func (r *inMemoryRepo) FindSessionByRefreshToken(_ context.Context, refreshToken string) (*domain.Session, error) {
	session, exists := r.sessions[refreshToken]
	if !exists {
		return nil, nil
	}
	copy := session
	return &copy, nil
}

func (r *inMemoryRepo) RevokeSession(_ context.Context, sessionID uuid.UUID) error {
	now := time.Now().UTC()
	for token, session := range r.sessions {
		if session.ID == sessionID {
			session.RevokedAt = &now
			r.sessions[token] = session
			return nil
		}
	}
	return errors.New("session not found")
}

func TestRegisterValidation(t *testing.T) {
	svc := NewService(newInMemoryRepo(), config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop())

	_, err := svc.Register(context.Background(), RegisterInput{
		Email:    "",
		Password: "short",
	})
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("expected invalid input error, got %v", err)
	}
}

func TestRegisterAndLoginFlow(t *testing.T) {
	repo := newInMemoryRepo()
	svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop())

	registered, err := svc.Register(context.Background(), RegisterInput{
		Email:       "user@example.com",
		Password:    "password123",
		DisplayName: "Seen User",
	})
	if err != nil {
		t.Fatalf("register failed: %v", err)
	}

	if registered.AccessToken == "" || registered.RefreshToken == "" {
		t.Fatalf("expected issued tokens")
	}

	loggedIn, err := svc.Login(context.Background(), LoginInput{
		Email:    "user@example.com",
		Password: "password123",
	})
	if err != nil {
		t.Fatalf("login failed: %v", err)
	}

	if loggedIn.AccessToken == "" {
		t.Fatalf("expected login access token")
	}
}

func TestLoginWrongPassword(t *testing.T) {
	repo := newInMemoryRepo()
	svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop())

	_, err := svc.Register(context.Background(), RegisterInput{
		Email:    "user@example.com",
		Password: "password123",
	})
	if err != nil {
		t.Fatalf("register failed: %v", err)
	}

	_, err = svc.Login(context.Background(), LoginInput{
		Email:    "user@example.com",
		Password: "wrongpass",
	})
	if !errors.Is(err, ErrInvalidCredentials) {
		t.Fatalf("expected invalid credentials error, got %v", err)
	}
}

func TestUserFromAccessToken(t *testing.T) {
	repo := newInMemoryRepo()
	svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop())

	authResult, err := svc.Register(context.Background(), RegisterInput{
		Email:    "user@example.com",
		Password: "password123",
	})
	if err != nil {
		t.Fatalf("register failed: %v", err)
	}

	user, err := svc.UserFromAccessToken(context.Background(), authResult.AccessToken)
	if err != nil {
		t.Fatalf("user from access token failed: %v", err)
	}

	if user.Email != "user@example.com" {
		t.Fatalf("expected user email, got %s", user.Email)
	}
}
