package auth

import (
	"context"
	"errors"
	"testing"

	"gitlab.com/dynaweb/ains-microservices/pkg/config"
	"gitlab.com/dynaweb/ains-microservices/pkg/delima"
	"gitlab.com/dynaweb/ains-microservices/pkg/jwt"
	"gitlab.com/dynaweb/ains-microservices/pkg/oauth2"
)

func testService(t *testing.T) (*Service, *jwt.Manager) {
	t.Helper()
	cfg := &config.Config{}
	cfg.JWT.Secret = "test-secret"
	cfg.JWT.Issuer = "ains-test"
	cfg.JWT.AccessTTL = 60000000000
	cfg.JWT.RefreshTTL = 3600000000000
	tokens := jwt.NewManager(cfg)
	store := NewMemoryStore()
	svc := NewService(store, store, delima.New("", ""), tokens, NewMemoryDenylist(), "/web/home")
	return svc, tokens
}

func TestRoleFromEmailType(t *testing.T) {
	cases := map[string]string{
		"m-123@moe-dl.edu.my":      RoleMurid,
		"g-abc@moe-dl.edu.my":      RoleTeacher,
		"jpn-johor@moe-dl.edu.my":  RoleJPN,
		"ppd-kl@moe-dl.edu.my":     RolePPD,
		"bstp-admin@moe-dl.edu.my": RoleKPM,
	}
	for email, want := range cases {
		if got := ResolveRole(email, nil); got != want {
			t.Errorf("ResolveRole(%q) = %q, want %q", email, got, want)
		}
	}
}

func TestResolveRoleStudentFromDelima(t *testing.T) {
	duser := &delima.User{Role: delima.Role{Text: "student"}}
	if got := ResolveRole("whatever-99@x.com", duser); got != RoleMurid {
		t.Fatalf("got %q, want murid", got)
	}
}

func TestResolveRoleOtherUsersUsesEmailType(t *testing.T) {
	duser := &delima.User{Role: delima.Role{Text: "other-users"}}
	if got := ResolveRole("ppd-kl@moe-dl.edu.my", duser); got != RolePPD {
		t.Fatalf("got %q, want ppd", got)
	}
}

func TestCompleteLoginMurid(t *testing.T) {
	svc, tokens := testService(t)
	info := &oauth2.UserInfo{Email: "m-100@moe-dl.edu.my", Name: "Aminah", Picture: "http://img"}

	sess, err := svc.CompleteLogin(context.Background(), info, "")
	if err != nil {
		t.Fatal(err)
	}
	if sess.Role != RoleMurid {
		t.Fatalf("role = %q, want murid", sess.Role)
	}
	if sess.Redirect != "/web/murid-dashboard" {
		t.Fatalf("redirect = %q", sess.Redirect)
	}
	if sess.AccessToken == "" || sess.RefreshToken == "" {
		t.Fatal("expected tokens")
	}
	claims, err := tokens.ParseAccess(sess.AccessToken)
	if err != nil {
		t.Fatal(err)
	}
	if claims.UserID != sess.UserID || claims.Role != RoleMurid {
		t.Fatalf("unexpected claims: %+v", claims)
	}
}

func TestCompleteLoginRejectsUnknownRole(t *testing.T) {
	svc, _ := testService(t)
	info := &oauth2.UserInfo{Email: "alien-1@example.com", Name: "X"}

	sess, err := svc.CompleteLogin(context.Background(), info, "")
	if !errors.Is(err, ErrRoleNotAllowed) {
		t.Fatalf("expected ErrRoleNotAllowed, got %v", err)
	}
	if sess == nil || sess.Redirect != "/web/auth-failed" {
		t.Fatalf("expected failed redirect, got %+v", sess)
	}
}

func TestCompleteLoginMissingEmail(t *testing.T) {
	svc, _ := testService(t)
	if _, err := svc.CompleteLogin(context.Background(), &oauth2.UserInfo{}, ""); !errors.Is(err, ErrMissingEmail) {
		t.Fatalf("expected ErrMissingEmail, got %v", err)
	}
}

func TestCompleteLoginUpsertsSameUser(t *testing.T) {
	svc, _ := testService(t)
	info := &oauth2.UserInfo{Email: "g-a@moe-dl.edu.my", Name: "Guru"}

	first, err := svc.CompleteLogin(context.Background(), info, "")
	if err != nil {
		t.Fatal(err)
	}
	info.Name = "Guru Baharu"
	second, err := svc.CompleteLogin(context.Background(), info, "")
	if err != nil {
		t.Fatal(err)
	}
	if first.UserID != second.UserID {
		t.Fatalf("expected same user id, got %d and %d", first.UserID, second.UserID)
	}
}

func TestMemoryDenylist(t *testing.T) {
	dl := NewMemoryDenylist()
	ctx := context.Background()

	if revoked, _ := dl.Revoked(ctx, "jti-1"); revoked {
		t.Fatal("should not be revoked yet")
	}
	if err := dl.Revoke(ctx, "jti-1", 3600000000000); err != nil {
		t.Fatal(err)
	}
	if revoked, _ := dl.Revoked(ctx, "jti-1"); !revoked {
		t.Fatal("should be revoked")
	}
	tokens := jwt.NewManager(testConfig())
	access, _ := tokens.IssueAccess(jwt.Identity{UserID: 1, Role: RoleMurid})
	claims, err := tokens.ParseAccess(access)
	if err != nil {
		t.Fatal(err)
	}
	if claims.ID == "" {
		t.Fatal("expected jti to be set")
	}
}

func testConfig() *config.Config {
	cfg := &config.Config{}
	cfg.JWT.Secret = "s"
	cfg.JWT.Issuer = "i"
	cfg.JWT.AccessTTL = 60000000000
	cfg.JWT.RefreshTTL = 3600000000000
	return cfg
}
