package security

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

// stubAuthenticator is a configurable Authenticator for testing.
type stubAuthenticator struct {
	userCtx *UserContext
	err     error
}

func (s *stubAuthenticator) Authenticate(_ *http.Request) (*UserContext, error) {
	return s.userCtx, s.err
}

func (s *stubAuthenticator) Login(_ context.Context, _ LoginRequest) (*LoginResponse, error) {
	if s.err != nil {
		return nil, s.err
	}
	return &LoginResponse{Token: "tok"}, nil
}

func (s *stubAuthenticator) LoginWithCookie(ctx context.Context, req LoginRequest, _ http.ResponseWriter) (*LoginResponse, error) {
	return s.Login(ctx, req)
}

func (s *stubAuthenticator) Logout(_ context.Context, _ LogoutRequest) error {
	return s.err
}

func (s *stubAuthenticator) LogoutWithCookie(ctx context.Context, req LogoutRequest, _ http.ResponseWriter) error {
	return s.Logout(ctx, req)
}

func (s *stubAuthenticator) SetAuthenticateCallback(_ func(r *http.Request) (*UserContext, error)) {}

func TestChainAuthenticator_Authenticate(t *testing.T) {
	successCtx := &UserContext{UserID: 42, UserName: "alice"}
	failStub := &stubAuthenticator{err: fmt.Errorf("no token")}
	okStub := &stubAuthenticator{userCtx: successCtx}

	t.Run("primary succeeds", func(t *testing.T) {
		chain := NewChainAuthenticator(okStub, failStub)
		req := httptest.NewRequest("GET", "/", nil)

		uc, err := chain.Authenticate(req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if uc.UserID != 42 {
			t.Errorf("expected UserID 42, got %d", uc.UserID)
		}
	})

	t.Run("primary fails, secondary succeeds", func(t *testing.T) {
		chain := NewChainAuthenticator(failStub, okStub)
		req := httptest.NewRequest("GET", "/", nil)

		uc, err := chain.Authenticate(req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if uc.UserID != 42 {
			t.Errorf("expected UserID 42, got %d", uc.UserID)
		}
	})

	t.Run("all fail", func(t *testing.T) {
		chain := NewChainAuthenticator(failStub, failStub)
		req := httptest.NewRequest("GET", "/", nil)

		_, err := chain.Authenticate(req)
		if err == nil {
			t.Fatal("expected error when all authenticators fail")
		}
	})

	t.Run("three in chain, first two fail", func(t *testing.T) {
		chain := NewChainAuthenticator(failStub, failStub, okStub)
		req := httptest.NewRequest("GET", "/", nil)

		uc, err := chain.Authenticate(req)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if uc.UserName != "alice" {
			t.Errorf("expected UserName alice, got %s", uc.UserName)
		}
	})
}

func TestChainAuthenticator_LoginLogout(t *testing.T) {
	primary := &stubAuthenticator{userCtx: &UserContext{UserID: 1}}
	secondary := &stubAuthenticator{userCtx: &UserContext{UserID: 2}}
	chain := NewChainAuthenticator(primary, secondary)
	ctx := context.Background()

	t.Run("login delegates to primary", func(t *testing.T) {
		resp, err := chain.Login(ctx, LoginRequest{Username: "u", Password: "p"})
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if resp.Token != "tok" {
			t.Errorf("expected token from primary, got %s", resp.Token)
		}
	})

	t.Run("logout delegates to primary", func(t *testing.T) {
		if err := chain.Logout(ctx, LogoutRequest{}); err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
	})

	t.Run("login error from primary is returned", func(t *testing.T) {
		failPrimary := &stubAuthenticator{err: fmt.Errorf("db down")}
		chain2 := NewChainAuthenticator(failPrimary, secondary)
		_, err := chain2.Login(ctx, LoginRequest{})
		if err == nil {
			t.Fatal("expected error from primary login failure")
		}
	})
}
