// Package permission provides tests for the permission manager.
package permission

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

	"AutoSSO/internal/config"
	"AutoSSO/internal/feishu"
	"AutoSSO/internal/pkg/logger"
	"AutoSSO/internal/provider"
	"AutoSSO/internal/state"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockProvider is a mock implementation of the provider.Provider interface.
type MockProvider struct {
	mock.Mock
}

func (m *MockProvider) Name() string {
	args := m.Called()
	return args.String(0)
}

func (m *MockProvider) AddPermission(ctx context.Context, req *provider.PermissionRequest) error {
	args := m.Called(ctx, req)
	return args.Error(0)
}

func (m *MockProvider) RemovePermission(ctx context.Context, req *provider.PermissionRequest) error {
	args := m.Called(ctx, req)
	return args.Error(0)
}

func (m *MockProvider) GetUserID(ctx context.Context, email string) (string, error) {
	args := m.Called(ctx, email)
	return args.String(0), args.Error(1)
}

func (m *MockProvider) ValidateCredentials(ctx context.Context) error {
	args := m.Called(ctx)
	return args.Error(0)
}

func (m *MockProvider) Close() error {
	args := m.Called()
	return args.Error(0)
}

// MockStore is a mock implementation of the state.Store interface.
type MockStore struct {
	mock.Mock
	mu              sync.Mutex
	pendingRemovals []*state.PendingRemoval
}

func (m *MockStore) SaveInstance(ctx context.Context, instance *state.ApprovalInstance) error {
	args := m.Called(ctx, instance)
	return args.Error(0)
}

func (m *MockStore) GetInstance(ctx context.Context, instanceID string) (*state.ApprovalInstance, error) {
	args := m.Called(ctx, instanceID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*state.ApprovalInstance), args.Error(1)
}

func (m *MockStore) ListInstances(ctx context.Context, filter *state.InstanceFilter) ([]*state.ApprovalInstance, error) {
	args := m.Called(ctx, filter)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]*state.ApprovalInstance), args.Error(1)
}

func (m *MockStore) DeleteInstance(ctx context.Context, instanceID string) error {
	args := m.Called(ctx, instanceID)
	return args.Error(0)
}

func (m *MockStore) SavePendingRemoval(ctx context.Context, removal *state.PendingRemoval) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	args := m.Called(ctx, removal)
	if args.Error(0) == nil {
		removal.ID = int64(len(m.pendingRemovals) + 1)
		m.pendingRemovals = append(m.pendingRemovals, removal)
	}
	return args.Error(0)
}

func (m *MockStore) GetPendingRemovals(ctx context.Context) ([]*state.PendingRemoval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	args := m.Called(ctx)
	if args.Get(0) != nil {
		return args.Get(0).([]*state.PendingRemoval), args.Error(1)
	}
	return m.pendingRemovals, args.Error(1)
}

func (m *MockStore) DeletePendingRemoval(ctx context.Context, id int64) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	args := m.Called(ctx, id)
	if args.Error(0) == nil {
		// Remove from internal list
		for i, r := range m.pendingRemovals {
			if r.ID == id {
				m.pendingRemovals = append(m.pendingRemovals[:i], m.pendingRemovals[i+1:]...)
				break
			}
		}
	}
	return args.Error(0)
}

func (m *MockStore) SaveDeletedInstance(ctx context.Context, instanceID string) error {
	args := m.Called(ctx, instanceID)
	return args.Error(0)
}

func (m *MockStore) IsInstanceDeleted(ctx context.Context, instanceID string) (bool, error) {
	args := m.Called(ctx, instanceID)
	return args.Bool(0), args.Error(1)
}

func (m *MockStore) Close() error {
	args := m.Called()
	return args.Error(0)
}

// Helper function to create a test configuration
func createTestConfig() *config.Config {
	return &config.Config{
		Feishu: config.FeishuConfig{
			AppID:        "test-app-id",
			AppSecret:    "test-app-secret",
			ApprovalCode: "test-approval-code",
			ChatID:       "test-chat-id",
			BaseURL:      "https://open.feishu.cn",
		},
		Runtime: config.RuntimeConfig{
			GracePeriodDays: 3,
			WhitelistEmails: []string{"security@example.com", "admin@example.com"},
		},
		PermissionMappings: []config.PermissionMapping{
			{FeishuName: "viewonly", RealName: "DBaaS-Prod-ViewOnly-Role"},
			{FeishuName: "oncall", RealName: "DBaaS-Prod-OnCall-Role"},
			{FeishuName: "billing", RealName: "DBaaS-Prod-Billing-Role"},
		},
		Providers: config.ProvidersConfig{
			AWS: config.AWSConfig{
				Accounts: []config.CloudAccount{
					{ID: "557537366020", Name: "prod-main", Type: "prod"},
				},
			},
		},
	}
}

// Helper function to create a test logger
func createTestLogger() *logger.Logger {
	return logger.NewLogger(logger.Config{
		Level:       logger.DebugLevel,
		PrettyPrint: false,
	})
}

// Helper function to create a test registry with mock provider
func createTestRegistry(cfg *config.Config, mockProv *MockProvider) *provider.Registry {
	registry, _ := provider.NewRegistry(cfg)
	registry.RegisterProvider(provider.ProviderAWS, mockProv)
	return registry
}

// TestNewManager tests the creation of a new Manager instance.
func TestNewManager(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	assert.NotNil(t, manager)
	assert.NotNil(t, manager.providers)
	assert.NotNil(t, manager.state)
	assert.NotNil(t, manager.config)
	assert.NotNil(t, manager.logger)
}

// TestAddPermissions_Success tests successful permission addition.
func TestAddPermissions_Success(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Setup mock expectations
	mockProv.On("AddPermission", mock.Anything, mock.MatchedBy(func(req *provider.PermissionRequest) bool {
		return req.AccountID == "557537366020" && req.UserEmail == "user@example.com"
	})).Return(nil)

	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{}, nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role"},
		CloudType:      "AWS",
		IsApproved:     true,
	}

	err := manager.AddPermissions(context.Background(), data)

	assert.NoError(t, err)
	mockProv.AssertExpectations(t)
	mockStore.AssertExpectations(t)
}

// TestAddPermissions_NilData tests that AddPermissions returns error for nil data.
func TestAddPermissions_NilData(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	err := manager.AddPermissions(context.Background(), nil)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot be nil")
}

// TestAddPermissions_InvalidData tests that AddPermissions returns error for invalid data.
func TestAddPermissions_InvalidData(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	// Missing required fields
	data := &feishu.ApprovalData{
		Email: "user@example.com",
		// Missing AppType, AccountID, PermissionSets
	}

	err := manager.AddPermissions(context.Background(), data)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid approval data")
}

// TestAddPermissions_ProviderError tests handling of provider errors.
func TestAddPermissions_ProviderError(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Setup mock to return error
	mockProv.On("AddPermission", mock.Anything, mock.Anything).Return(errors.New("provider error"))
	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{}, nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role"},
		CloudType:      "AWS",
		IsApproved:     true,
	}

	err := manager.AddPermissions(context.Background(), data)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "failed to add")
}

// TestAddPermissions_RemovesPendingRemoval tests that AddPermissions removes pending removal.
func TestAddPermissions_RemovesPendingRemoval(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Setup pending removal that should be removed
	pendingRemoval := &state.PendingRemoval{
		ID:            1,
		AccountID:     "557537366020",
		Email:         "user@example.com",
		PermissionSet: "DBaaS-Prod-ViewOnly-Role",
		CloudType:     "AWS",
	}

	mockProv.On("AddPermission", mock.Anything, mock.Anything).Return(nil)
	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{pendingRemoval}, nil)
	mockStore.On("DeletePendingRemoval", mock.Anything, int64(1)).Return(nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role"},
		CloudType:      "AWS",
		IsApproved:     true,
	}

	err := manager.AddPermissions(context.Background(), data)

	assert.NoError(t, err)
	mockStore.AssertCalled(t, "DeletePendingRemoval", mock.Anything, int64(1))
}

// TestRemovePermissions_Success tests successful permission removal.
func TestRemovePermissions_Success(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	mockProv.On("RemovePermission", mock.Anything, mock.MatchedBy(func(req *provider.PermissionRequest) bool {
		return req.AccountID == "557537366020" && req.UserEmail == "user@example.com"
	})).Return(nil)

	mockStore.On("DeletePendingRemoval", mock.Anything, int64(1)).Return(nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	removal := &state.PendingRemoval{
		ID:            1,
		AccountID:     "557537366020",
		Email:         "user@example.com",
		PermissionSet: "DBaaS-Prod-ViewOnly-Role",
		CloudType:     "AWS",
	}

	err := manager.RemovePermissions(context.Background(), removal)

	assert.NoError(t, err)
	mockProv.AssertExpectations(t)
	mockStore.AssertExpectations(t)
}

// TestRemovePermissions_NilRemoval tests that RemovePermissions returns error for nil removal.
func TestRemovePermissions_NilRemoval(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	err := manager.RemovePermissions(context.Background(), nil)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot be nil")
}

// TestRemovePermissions_ProviderError tests handling of provider errors during removal.
func TestRemovePermissions_ProviderError(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	mockProv.On("RemovePermission", mock.Anything, mock.Anything).Return(errors.New("provider error"))

	manager := NewManager(registry, nil, mockStore, cfg, log)

	removal := &state.PendingRemoval{
		ID:            1,
		AccountID:     "557537366020",
		Email:         "user@example.com",
		PermissionSet: "DBaaS-Prod-ViewOnly-Role",
		CloudType:     "AWS",
	}

	err := manager.RemovePermissions(context.Background(), removal)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "failed to remove permission")
}

// TestScheduleRemoval_Success tests successful scheduling of permission removal.
func TestScheduleRemoval_Success(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	mockStore.On("SavePendingRemoval", mock.Anything, mock.MatchedBy(func(removal *state.PendingRemoval) bool {
		return removal.AccountID == "557537366020" &&
			removal.Email == "user@example.com" &&
			removal.PermissionSet == "DBaaS-Prod-ViewOnly-Role"
	})).Return(nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role"},
		CloudType:      "AWS",
		EndTime:        time.Now().Add(-24 * time.Hour), // Expired yesterday
	}

	err := manager.ScheduleRemoval(context.Background(), data)

	assert.NoError(t, err)
	mockStore.AssertExpectations(t)
}

// TestScheduleRemoval_WhitelistedUser tests that whitelisted users are skipped.
func TestScheduleRemoval_WhitelistedUser(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Note: SavePendingRemoval should NOT be called for whitelisted users
	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "security@example.com", // Whitelisted email
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role"},
		CloudType:      "AWS",
		EndTime:        time.Now().Add(-24 * time.Hour),
	}

	err := manager.ScheduleRemoval(context.Background(), data)

	assert.NoError(t, err)
	mockStore.AssertNotCalled(t, "SavePendingRemoval", mock.Anything, mock.Anything)
}

// TestScheduleRemoval_NilData tests that ScheduleRemoval returns error for nil data.
func TestScheduleRemoval_NilData(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	err := manager.ScheduleRemoval(context.Background(), nil)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot be nil")
}

// TestIsWhitelisted tests the whitelist checking functionality.
func TestIsWhitelisted(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	tests := []struct {
		email    string
		expected bool
	}{
		{"security@example.com", true},
		{"admin@example.com", true},
		{"SECURITY@EXAMPLE.COM", true}, // Case insensitive
		{"user@example.com", false},
		{"", false},
	}

	for _, tt := range tests {
		t.Run(tt.email, func(t *testing.T) {
			result := manager.isWhitelisted(tt.email)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestConvertFeishuPermission tests the permission name conversion.
func TestConvertFeishuPermission(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	tests := []struct {
		feishuName string
		expected   string
	}{
		{"viewonly", "DBaaS-Prod-ViewOnly-Role"},
		{"VIEWONLY", "DBaaS-Prod-ViewOnly-Role"}, // Case insensitive
		{"oncall", "DBaaS-Prod-OnCall-Role"},
		{"billing", "DBaaS-Prod-Billing-Role"},
		{"unknown", "unknown"}, // No mapping, return original
	}

	for _, tt := range tests {
		t.Run(tt.feishuName, func(t *testing.T) {
			result := manager.ConvertFeishuPermission(tt.feishuName)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestGetPendingRemovals tests retrieving ready pending removals.
func TestGetPendingRemovals(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Create removals with different scheduled times
	readyRemoval := &state.PendingRemoval{
		ID:          1,
		AccountID:   "557537366020",
		Email:       "user1@example.com",
		ScheduledAt: time.Now().Add(-1 * time.Hour), // Ready (past)
	}
	notReadyRemoval := &state.PendingRemoval{
		ID:          2,
		AccountID:   "557537366020",
		Email:       "user2@example.com",
		ScheduledAt: time.Now().Add(24 * time.Hour), // Not ready (future)
	}

	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{readyRemoval, notReadyRemoval}, nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	removals, err := manager.GetPendingRemovals(context.Background())

	assert.NoError(t, err)
	assert.Len(t, removals, 1)
	assert.Equal(t, int64(1), removals[0].ID)
}

// TestProcessPendingRemovals tests processing all ready pending removals.
func TestProcessPendingRemovals(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	readyRemoval := &state.PendingRemoval{
		ID:            1,
		AccountID:     "557537366020",
		Email:         "user@example.com",
		PermissionSet: "DBaaS-Prod-ViewOnly-Role",
		CloudType:     "AWS",
		ScheduledAt:   time.Now().Add(-1 * time.Hour),
	}

	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{readyRemoval}, nil)
	mockProv.On("RemovePermission", mock.Anything, mock.Anything).Return(nil)
	mockStore.On("DeletePendingRemoval", mock.Anything, int64(1)).Return(nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	processed, errs := manager.ProcessPendingRemovals(context.Background())

	assert.Equal(t, 1, processed)
	assert.Empty(t, errs)
}

// TestAddPermissions_MultiplePermissions tests adding multiple permissions.
func TestAddPermissions_MultiplePermissions(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// Setup mock expectations for multiple permissions
	mockProv.On("AddPermission", mock.Anything, mock.Anything).Return(nil).Times(3)
	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{}, nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role", "DBaaS-Prod-OnCall-Role", "DBaaS-Prod-Billing-Role"},
		CloudType:      "AWS",
		IsApproved:     true,
	}

	err := manager.AddPermissions(context.Background(), data)

	assert.NoError(t, err)
	mockProv.AssertNumberOfCalls(t, "AddPermission", 3)
}

// TestAddPermissions_PartialFailure tests partial failure when adding multiple permissions.
func TestAddPermissions_PartialFailure(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	// First permission succeeds, second fails
	mockProv.On("AddPermission", mock.Anything, mock.MatchedBy(func(req *provider.PermissionRequest) bool {
		return req.PermissionSets[0] == "DBaaS-Prod-ViewOnly-Role"
	})).Return(nil).Once()

	mockProv.On("AddPermission", mock.Anything, mock.MatchedBy(func(req *provider.PermissionRequest) bool {
		return req.PermissionSets[0] == "DBaaS-Prod-OnCall-Role"
	})).Return(errors.New("provider error")).Once()

	mockStore.On("GetPendingRemovals", mock.Anything).Return([]*state.PendingRemoval{}, nil)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	data := &feishu.ApprovalData{
		Email:          "user@example.com",
		AppType:        true,
		AccountID:      "557537366020",
		PermissionSets: []string{"DBaaS-Prod-ViewOnly-Role", "DBaaS-Prod-OnCall-Role"},
		CloudType:      "AWS",
		IsApproved:     true,
	}

	err := manager.AddPermissions(context.Background(), data)

	// Should return partial error
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "failed to add some permissions")
}

// TestGetProviderType tests getting provider type for an account.
func TestGetProviderType(t *testing.T) {
	cfg := createTestConfig()
	log := createTestLogger()
	mockProv := new(MockProvider)
	registry := createTestRegistry(cfg, mockProv)
	mockStore := new(MockStore)

	manager := NewManager(registry, nil, mockStore, cfg, log)

	// Test known account
	provType, found := manager.GetProviderType("557537366020")
	assert.True(t, found)
	assert.Equal(t, provider.ProviderAWS, provType)

	// Test unknown account
	_, found = manager.GetProviderType("unknown-account")
	assert.False(t, found)
}
