package di

import (
	"context"
	"os"
	"testing"
	"time"

	"git.dcentral.systems/toolz/goplt/pkg/config"
	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"go.uber.org/fx"
)

func TestProvideConfig(t *testing.T) {
	t.Parallel()

	// Set environment variable
	originalEnv := os.Getenv("ENVIRONMENT")
	defer func() {
		if err := os.Setenv("ENVIRONMENT", originalEnv); err != nil {
			t.Logf("Failed to restore environment variable: %v", err)
		}
	}()

	if err := os.Setenv("ENVIRONMENT", "development"); err != nil {
		t.Fatalf("Failed to set environment variable: %v", err)
	}

	// Create a test app with ProvideConfig
	app := fx.New(
		ProvideConfig(),
		fx.Invoke(func(cfg config.ConfigProvider) {
			if cfg == nil {
				t.Error("ConfigProvider is nil")
			}
		}),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		// It's okay if it fails due to missing config files
		// We're just checking that the provider is registered
		t.Logf("Start failed (may be expected in test env): %v", err)
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}
}

func TestProvideConfig_DefaultEnvironment(t *testing.T) {
	t.Parallel()

	// Unset environment variable
	originalEnv := os.Getenv("ENVIRONMENT")
	defer func() {
		if err := os.Setenv("ENVIRONMENT", originalEnv); err != nil {
			t.Logf("Failed to restore environment variable: %v", err)
		}
	}()

	if err := os.Unsetenv("ENVIRONMENT"); err != nil {
		t.Fatalf("Failed to unset environment variable: %v", err)
	}

	// Create a test app with ProvideConfig
	app := fx.New(
		ProvideConfig(),
		fx.Invoke(func(cfg config.ConfigProvider) {
			if cfg == nil {
				t.Error("ConfigProvider is nil")
			}
		}),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		// It's okay if it fails due to missing config files
		t.Logf("Start failed (may be expected in test env): %v", err)
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}
}

func TestProvideLogger(t *testing.T) {
	t.Parallel()

	// Create a mock config provider
	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"logging.level":  "info",
			"logging.format": "json",
		},
	}

	// Create a test app with ProvideLogger
	app := fx.New(
		fx.Provide(func() config.ConfigProvider {
			return mockConfig
		}),
		ProvideLogger(),
		fx.Invoke(func(log logger.Logger) {
			if log == nil {
				t.Error("Logger is nil")
			}

			// Test that logger works
			log.Info("test message")
		}),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		t.Fatalf("Start failed: %v", err)
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}
}

func TestProvideLogger_DefaultValues(t *testing.T) {
	t.Parallel()

	// Create a mock config provider with missing values
	mockConfig := &mockConfigProvider{
		values: map[string]any{},
	}

	// Create a test app with ProvideLogger
	app := fx.New(
		fx.Provide(func() config.ConfigProvider {
			return mockConfig
		}),
		ProvideLogger(),
		fx.Invoke(func(log logger.Logger) {
			if log == nil {
				t.Error("Logger is nil")
			}

			// Test that logger works with defaults
			log.Info("test message with defaults")
		}),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		t.Fatalf("Start failed: %v", err)
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}
}

func TestCoreModule(t *testing.T) {
	t.Parallel()

	// Create a test app with CoreModule
	app := fx.New(
		CoreModule(),
		fx.Invoke(func(
			cfg config.ConfigProvider,
			log logger.Logger,
		) {
			if cfg == nil {
				t.Error("ConfigProvider is nil")
			}
			if log == nil {
				t.Error("Logger is nil")
			}
		}),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		// It's okay if it fails due to missing config files
		t.Logf("Start failed (may be expected in test env): %v", err)
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}
}

func TestRegisterLifecycleHooks(t *testing.T) {
	t.Parallel()

	// Create a mock logger
	mockLogger := &mockLogger{}

	// Create a test app with lifecycle hooks
	app := fx.New(
		fx.Provide(func() logger.Logger {
			return mockLogger
		}),
		fx.Invoke(RegisterLifecycleHooks),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Start the app
	if err := app.Start(ctx); err != nil {
		t.Fatalf("Start failed: %v", err)
	}

	// Verify that OnStart was called
	if !mockLogger.onStartCalled {
		t.Error("OnStart hook was not called")
	}

	// Stop the app
	if err := app.Stop(ctx); err != nil {
		t.Errorf("Stop failed: %v", err)
	}

	// Verify that OnStop was called
	if !mockLogger.onStopCalled {
		t.Error("OnStop hook was not called")
	}
}

// mockConfigProvider is a mock implementation of ConfigProvider for testing
type mockConfigProvider struct {
	values map[string]any
}

func (m *mockConfigProvider) Get(key string) any {
	return m.values[key]
}

func (m *mockConfigProvider) Unmarshal(_ any) error {
	return nil
}

func (m *mockConfigProvider) GetString(key string) string {
	if val, ok := m.values[key]; ok {
		if str, ok := val.(string); ok {
			return str
		}
	}
	return ""
}

func (m *mockConfigProvider) GetInt(key string) int {
	if val, ok := m.values[key]; ok {
		if i, ok := val.(int); ok {
			return i
		}
	}
	return 0
}

func (m *mockConfigProvider) GetBool(key string) bool {
	if val, ok := m.values[key]; ok {
		if b, ok := val.(bool); ok {
			return b
		}
	}
	return false
}

func (m *mockConfigProvider) GetStringSlice(key string) []string {
	if val, ok := m.values[key]; ok {
		if slice, ok := val.([]string); ok {
			return slice
		}
	}
	return nil
}

func (m *mockConfigProvider) GetDuration(key string) time.Duration {
	if val, ok := m.values[key]; ok {
		if d, ok := val.(time.Duration); ok {
			return d
		}
	}
	return 0
}

func (m *mockConfigProvider) IsSet(key string) bool {
	_, ok := m.values[key]
	return ok
}

// mockLogger is a mock implementation of Logger for testing
type mockLogger struct {
	onStartCalled bool
	onStopCalled  bool
}

func (m *mockLogger) Debug(_ string, _ ...logger.Field) {}
func (m *mockLogger) Info(msg string, _ ...logger.Field) {
	if msg == "Application starting" {
		m.onStartCalled = true
	}
	if msg == "Application shutting down" {
		m.onStopCalled = true
	}
}
func (m *mockLogger) Warn(_ string, _ ...logger.Field)  {}
func (m *mockLogger) Error(_ string, _ ...logger.Field) {}
func (m *mockLogger) With(_ ...logger.Field) logger.Logger {
	return m
}
func (m *mockLogger) WithContext(_ context.Context) logger.Logger {
	return m
}
