package lib

import (
	"testing"
	"time"

	"github.com/google/uuid"
)

func TestCheckAccess(t *testing.T) {
	db, cfg := testDB(t)

	// Create owner
	ownerID := uuid.New().String()
	now := time.Now().UnixMilli()
	owner := &User{
		UserID:    ownerID,
		Email:     "owner@test.com",
		Name:      "Owner",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, owner)
	projectID := testProject(t, db, cfg, ownerID)

	// IB admin can read
	err := CheckAccessRead(db, ownerID, projectID, "")
	if err != nil {
		t.Errorf("IB admin should have read access: %v", err)
	}

	// IB admin can write
	err = CheckAccessWrite(db, ownerID, projectID, "")
	if err != nil {
		t.Errorf("IB admin should have write access: %v", err)
	}

	// IB admin can delete
	err = CheckAccessDelete(db, ownerID, projectID, "")
	if err != nil {
		t.Errorf("IB admin should have delete access: %v", err)
	}

	// Create seller user
	sellerID := testUser(t, db, cfg, projectID, RoleSellerMember)

	// Seller can read
	err = CheckAccessRead(db, sellerID, projectID, "")
	if err != nil {
		t.Errorf("seller should have read access: %v", err)
	}

	// Seller can write
	err = CheckAccessWrite(db, sellerID, projectID, "")
	if err != nil {
		t.Errorf("seller should have write access: %v", err)
	}

	// Create buyer user (read-only)
	buyerID := testUser(t, db, cfg, projectID, RoleBuyerMember)

	// Buyer can read
	err = CheckAccessRead(db, buyerID, projectID, "")
	if err != nil {
		t.Errorf("buyer should have read access: %v", err)
	}

	// Buyer cannot write
	err = CheckAccessWrite(db, buyerID, projectID, "")
	if err != ErrAccessDenied {
		t.Errorf("buyer should NOT have write access, got: %v", err)
	}
}

func TestRoleHierarchy(t *testing.T) {
	// Verify hierarchy levels: buyer < seller_member < seller_admin < ib_analyst < ib_member < ib_admin
	expected := []struct {
		role  string
		level int
	}{
		{RoleObserver, 10},
		{RoleBuyerMember, 30},
		{RoleBuyerAdmin, 40},
		{RoleSellerMember, 50},
		{RoleSellerAdmin, 70},
		{RoleIBMember, 80},
		{RoleIBAdmin, 100},
	}

	for _, tc := range expected {
		level, ok := RoleHierarchy[tc.role]
		if !ok {
			t.Errorf("role %s not in hierarchy", tc.role)
			continue
		}
		if level != tc.level {
			t.Errorf("role %s: expected level %d, got %d", tc.role, tc.level, level)
		}
	}

	// Verify ordering
	if RoleHierarchy[RoleBuyerMember] >= RoleHierarchy[RoleSellerMember] {
		t.Error("buyer should be lower than seller_member")
	}
	if RoleHierarchy[RoleSellerMember] >= RoleHierarchy[RoleSellerAdmin] {
		t.Error("seller_member should be lower than seller_admin")
	}
	if RoleHierarchy[RoleSellerAdmin] >= RoleHierarchy[RoleIBMember] {
		t.Error("seller_admin should be lower than ib_member")
	}
	if RoleHierarchy[RoleIBMember] >= RoleHierarchy[RoleIBAdmin] {
		t.Error("ib_member should be lower than ib_admin")
	}
}

func TestCanGrantRole(t *testing.T) {
	tests := []struct {
		granter string
		target  string
		canDo   bool
	}{
		// IB admin can grant anything
		{RoleIBAdmin, RoleIBAdmin, true},
		{RoleIBAdmin, RoleIBMember, true},
		{RoleIBAdmin, RoleSellerAdmin, true},
		{RoleIBAdmin, RoleBuyerMember, true},

		// IB member can grant lower roles
		{RoleIBMember, RoleIBAdmin, false},
		{RoleIBMember, RoleIBMember, true},
		{RoleIBMember, RoleSellerAdmin, true},

		// Seller admin can grant seller and buyer roles
		{RoleSellerAdmin, RoleIBMember, false},
		{RoleSellerAdmin, RoleSellerAdmin, true},
		{RoleSellerAdmin, RoleSellerMember, true},
		{RoleSellerAdmin, RoleBuyerMember, true},

		// Buyer cannot grant higher roles
		{RoleBuyerAdmin, RoleSellerMember, false},
		{RoleBuyerAdmin, RoleBuyerAdmin, true},
		{RoleBuyerAdmin, RoleBuyerMember, true},
	}

	for _, tc := range tests {
		result := CanGrantRole(tc.granter, tc.target)
		if result != tc.canDo {
			t.Errorf("CanGrantRole(%s, %s) = %v, want %v", tc.granter, tc.target, result, tc.canDo)
		}
	}
}

func TestGrantRevoke(t *testing.T) {
	db, cfg := testDB(t)

	// Create admin
	adminID := uuid.New().String()
	now := time.Now().UnixMilli()
	admin := &User{
		UserID:    adminID,
		Email:     "admin@test.com",
		Name:      "Admin",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, admin)
	projectID := testProject(t, db, cfg, adminID)

	// Create user with no access
	userID := uuid.New().String()
	user := &User{
		UserID:    userID,
		Email:     "user@test.com",
		Name:      "User",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, user)

	// Verify no access
	err := CheckAccessRead(db, userID, projectID, "")
	if err != ErrAccessDenied {
		t.Error("user should have no access initially")
	}

	// Grant access
	accessID := uuid.New().String()
	access := &Access{
		ID:        accessID,
		ProjectID: projectID,
		UserID:    userID,
		Role:      RoleBuyerMember,
		Ops:       "r",
		CanGrant:  false,
		GrantedBy: adminID,
		GrantedAt: now,
	}
	if err := AccessGrant(db, access); err != nil {
		t.Fatalf("AccessGrant: %v", err)
	}

	// Verify access granted
	err = CheckAccessRead(db, userID, projectID, "")
	if err != nil {
		t.Errorf("user should have read access after grant: %v", err)
	}

	// Revoke access
	if err := AccessRevoke(db, accessID, adminID); err != nil {
		t.Fatalf("AccessRevoke: %v", err)
	}

	// Verify access revoked
	err = CheckAccessRead(db, userID, projectID, "")
	if err != ErrAccessDenied {
		t.Error("user should have no access after revoke")
	}
}

func TestIsBuyerRole(t *testing.T) {
	buyers := []string{RoleBuyerAdmin, RoleBuyerMember}
	nonBuyers := []string{RoleIBAdmin, RoleIBMember, RoleSellerAdmin, RoleSellerMember, RoleObserver}

	for _, role := range buyers {
		if !IsBuyerRole(role) {
			t.Errorf("%s should be buyer role", role)
		}
	}

	for _, role := range nonBuyers {
		if IsBuyerRole(role) {
			t.Errorf("%s should NOT be buyer role", role)
		}
	}
}

func TestGetUserHighestRole(t *testing.T) {
	db, cfg := testDB(t)

	// Create admin and project
	adminID := uuid.New().String()
	now := time.Now().UnixMilli()
	admin := &User{
		UserID:    adminID,
		Email:     "admin@test.com",
		Name:      "Admin",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, admin)
	projectID := testProject(t, db, cfg, adminID)

	// Admin's highest role should be ib_admin
	role, err := GetUserHighestRole(db, adminID, projectID)
	if err != nil {
		t.Fatalf("GetUserHighestRole: %v", err)
	}
	if role != RoleIBAdmin {
		t.Errorf("expected ib_admin, got %s", role)
	}

	// Create user with multiple roles
	userID := uuid.New().String()
	user := &User{
		UserID:    userID,
		Email:     "multi@test.com",
		Name:      "Multi Role User",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, user)

	// Grant buyer role
	AccessGrant(db, &Access{
		ID:        uuid.New().String(),
		ProjectID: projectID,
		UserID:    userID,
		Role:      RoleBuyerMember,
		Ops:       "r",
		GrantedBy: adminID,
		GrantedAt: now,
	})

	// Grant seller role (higher)
	AccessGrant(db, &Access{
		ID:        uuid.New().String(),
		ProjectID: projectID,
		UserID:    userID,
		Role:      RoleSellerMember,
		Ops:       "rw",
		GrantedBy: adminID,
		GrantedAt: now,
	})

	// Highest should be seller_member
	role, err = GetUserHighestRole(db, userID, projectID)
	if err != nil {
		t.Fatalf("GetUserHighestRole: %v", err)
	}
	if role != RoleSellerMember {
		t.Errorf("expected seller_member (highest), got %s", role)
	}

	// User with no access
	noAccessID := uuid.New().String()
	noAccessUser := &User{
		UserID:    noAccessID,
		Email:     "noaccess@test.com",
		Name:      "No Access",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, noAccessUser)

	_, err = GetUserHighestRole(db, noAccessID, projectID)
	if err != ErrAccessDenied {
		t.Errorf("expected ErrAccessDenied for user with no access, got %v", err)
	}
}

func TestWorkstreamAccess(t *testing.T) {
	db, cfg := testDB(t)

	// Create admin and project
	adminID := uuid.New().String()
	now := time.Now().UnixMilli()
	admin := &User{
		UserID:    adminID,
		Email:     "admin@test.com",
		Name:      "Admin",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, admin)
	projectID := testProject(t, db, cfg, adminID)

	// Create user with access to specific workstream only
	userID := uuid.New().String()
	user := &User{
		UserID:    userID,
		Email:     "ws@test.com",
		Name:      "Workstream User",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, user)

	workstreamID := "workstream-1"
	AccessGrant(db, &Access{
		ID:           uuid.New().String(),
		ProjectID:    projectID,
		WorkstreamID: workstreamID,
		UserID:       userID,
		Role:         RoleSellerMember,
		Ops:          "rw",
		GrantedBy:    adminID,
		GrantedAt:    now,
	})

	// User has access to their workstream
	_, err := CheckAccess(db, userID, projectID, workstreamID, "r")
	if err != nil {
		t.Errorf("user should have access to their workstream: %v", err)
	}

	// User should NOT have access to different workstream
	_, err = CheckAccess(db, userID, projectID, "different-workstream", "r")
	if err != ErrAccessDenied {
		t.Error("user should NOT have access to different workstream")
	}
}

// ---- Additional tests for super_admin, IsSuperAdmin, and ValidateOrgDomain ----

func TestSuperAdminBypassesCheckAccess(t *testing.T) {
	db, cfg := testDB(t)

	// Create a regular user who owns a project
	ownerID := uuid.New().String()
	now := time.Now().UnixMilli()
	owner := &User{
		UserID:    ownerID,
		Email:     "owner@test.com",
		Name:      "Owner",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, owner)
	projectID := testProject(t, db, cfg, ownerID)

	// Create super_admin user (no explicit project access)
	superAdminID := uuid.New().String()
	superAdmin := &User{
		UserID:    superAdminID,
		Email:     "superadmin@test.com",
		Name:      "Super Admin",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, superAdmin)

	// Grant super_admin role on a dummy project (super_admin is global but stored per project)
	dummyProjectID := uuid.New().String()
	AccessGrant(db, &Access{
		ID:        uuid.New().String(),
		ProjectID: dummyProjectID,
		UserID:    superAdminID,
		Role:      RoleSuperAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: "system",
		GrantedAt: now,
	})

	// Verify super_admin is recognized
	isSA, err := IsSuperAdmin(db, superAdminID)
	if err != nil {
		t.Fatalf("IsSuperAdmin failed: %v", err)
	}
	if !isSA {
		t.Error("User with super_admin role should be recognized as super admin")
	}

	// super_admin should have full access to any project without explicit grant
	access, err := CheckAccess(db, superAdminID, projectID, "", "r")
	if err != nil {
		t.Errorf("super_admin should have read access: %v", err)
	}
	if access == nil {
		t.Fatal("CheckAccess should return an Access object for super_admin")
	}
	if access.Role != RoleSuperAdmin {
		t.Errorf("Access role should be super_admin, got %s", access.Role)
	}
	if access.Ops != "rwdm" {
		t.Errorf("super_admin should have rwdm ops, got %s", access.Ops)
	}

	// Test all operations
	_, err = CheckAccess(db, superAdminID, projectID, "", "w")
	if err != nil {
		t.Errorf("super_admin should have write access: %v", err)
	}

	_, err = CheckAccess(db, superAdminID, projectID, "", "d")
	if err != nil {
		t.Errorf("super_admin should have delete access: %v", err)
	}

	_, err = CheckAccess(db, superAdminID, projectID, "", "m")
	if err != nil {
		t.Errorf("super_admin should have manage access: %v", err)
	}

	// Test convenience functions
	err = CheckAccessRead(db, superAdminID, projectID, "")
	if err != nil {
		t.Errorf("super_admin CheckAccessRead should pass: %v", err)
	}

	err = CheckAccessWrite(db, superAdminID, projectID, "")
	if err != nil {
		t.Errorf("super_admin CheckAccessWrite should pass: %v", err)
	}

	err = CheckAccessDelete(db, superAdminID, projectID, "")
	if err != nil {
		t.Errorf("super_admin CheckAccessDelete should pass: %v", err)
	}
}

func TestIsSuperAdmin(t *testing.T) {
	db, _ := testDB(t)
	now := time.Now().UnixMilli()

	// Create regular user
	regularID := uuid.New().String()
	regular := &User{
		UserID:    regularID,
		Email:     "regular@test.com",
		Name:      "Regular User",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, regular)

	// Create super admin user
	superID := uuid.New().String()
	superUser := &User{
		UserID:    superID,
		Email:     "super@test.com",
		Name:      "Super User",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, superUser)

	// Grant super_admin role
	AccessGrant(db, &Access{
		ID:        uuid.New().String(),
		ProjectID: "any-project",
		UserID:    superID,
		Role:      RoleSuperAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: "system",
		GrantedAt: now,
	})

	// Test IsSuperAdmin
	isSA, err := IsSuperAdmin(db, superID)
	if err != nil {
		t.Fatalf("IsSuperAdmin failed: %v", err)
	}
	if !isSA {
		t.Error("User with super_admin role should return true")
	}

	isRegularSA, err := IsSuperAdmin(db, regularID)
	if err != nil {
		t.Fatalf("IsSuperAdmin for regular user failed: %v", err)
	}
	if isRegularSA {
		t.Error("Regular user should not be super admin")
	}

	// Test with ib_admin (should NOT be super admin)
	ibAdminID := uuid.New().String()
	ibAdmin := &User{
		UserID:    ibAdminID,
		Email:     "ibadmin@test.com",
		Name:      "IB Admin",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, ibAdmin)
	AccessGrant(db, &Access{
		ID:        uuid.New().String(),
		ProjectID: "test-project",
		UserID:    ibAdminID,
		Role:      RoleIBAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: "system",
		GrantedAt: now,
	})

	isIBSA, err := IsSuperAdmin(db, ibAdminID)
	if err != nil {
		t.Fatalf("IsSuperAdmin for ib_admin failed: %v", err)
	}
	if isIBSA {
		t.Error("ib_admin should NOT be super admin")
	}
}

func TestIsSuperAdminRevokedGrant(t *testing.T) {
	db, _ := testDB(t)
	now := time.Now().UnixMilli()

	// Create user
	userID := uuid.New().String()
	user := &User{
		UserID:    userID,
		Email:     "revoke-test@test.com",
		Name:      "Revoke Test",
		Password:  "$2a$10$test",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	UserCreate(db, user)

	// Grant and then revoke super_admin
	accessID := uuid.New().String()
	AccessGrant(db, &Access{
		ID:        accessID,
		ProjectID: "any-project",
		UserID:    userID,
		Role:      RoleSuperAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: "system",
		GrantedAt: now,
	})

	// Should be super admin
	isSA, _ := IsSuperAdmin(db, userID)
	if !isSA {
		t.Error("Should be super admin before revocation")
	}

	// Revoke the grant
	AccessRevoke(db, accessID, "system")

	// Should no longer be super admin
	isSA, _ = IsSuperAdmin(db, userID)
	if isSA {
		t.Error("Should NOT be super admin after revocation")
	}
}

func TestSuperAdminCanGrantAnyRole(t *testing.T) {
	// super_admin (level 200) should be able to grant any role
	tests := []struct {
		target string
		canDo  bool
	}{
		{RoleSuperAdmin, true},
		{RoleIBAdmin, true},
		{RoleIBMember, true},
		{RoleSellerAdmin, true},
		{RoleSellerMember, true},
		{RoleBuyerAdmin, true},
		{RoleBuyerMember, true},
		{RoleObserver, true},
	}

	for _, tc := range tests {
		result := CanGrantRole(RoleSuperAdmin, tc.target)
		if result != tc.canDo {
			t.Errorf("CanGrantRole(super_admin, %s) = %v, want %v", tc.target, result, tc.canDo)
		}
	}
}
