package auth

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/jfraeys/fetch_ml/internal/auth"
	"gopkg.in/yaml.v3"
)

// ConfigWithAuth holds configuration with authentication
type ConfigWithAuth struct {
	Auth auth.Config `yaml:"auth"`
}

func TestUserManagerGenerateKey(t *testing.T) {
	// Create temporary config file
	tempDir := t.TempDir()
	configFile := filepath.Join(tempDir, "test_config.yaml")

	// Initial config with auth enabled
	config := ConfigWithAuth{
		Auth: auth.Config{
			Enabled: true,
			APIKeys: map[auth.Username]auth.APIKeyEntry{
				"existing_user": {
					Hash:  auth.APIKeyHash(auth.HashAPIKey("existing-key")),
					Admin: false,
				},
			},
		},
	}

	data, err := yaml.Marshal(config)
	if err != nil {
		t.Fatalf("Failed to marshal config: %v", err)
	}

	if err := os.WriteFile(configFile, data, 0600); err != nil {
		t.Fatalf("Failed to write config: %v", err)
	}

	// Test generate-key command
	configData, err := os.ReadFile(filepath.Clean(configFile))
	if err != nil {
		t.Fatalf("Failed to read config: %v", err)
	}

	var cfg ConfigWithAuth
	if err := yaml.Unmarshal(configData, &cfg); err != nil {
		t.Fatalf("Failed to parse config: %v", err)
	}

	// Generate API key using new Argon2id method
	_, hashed, err := auth.GenerateAPIKey()
	if err != nil {
		t.Fatalf("Failed to generate API key: %v", err)
	}

	// Add to config with algorithm info
	if cfg.Auth.APIKeys == nil {
		cfg.Auth.APIKeys = make(map[auth.Username]auth.APIKeyEntry)
	}
	cfg.Auth.APIKeys[auth.Username("test_user")] = auth.APIKeyEntry{
		Hash:      auth.APIKeyHash(hashed.Hash),
		Salt:      hashed.Salt,
		Algorithm: string(hashed.Algorithm),
		Admin:     false,
	}

	// Save config
	updatedData, err := yaml.Marshal(cfg)
	if err != nil {
		t.Fatalf("Failed to marshal updated config: %v", err)
	}

	if err := os.WriteFile(configFile, updatedData, 0600); err != nil {
		t.Fatalf("Failed to write updated config: %v", err)
	}

	// Verify user was added
	savedData, err := os.ReadFile(configFile)
	if err != nil {
		t.Fatalf("Failed to read saved config: %v", err)
	}

	var savedCfg ConfigWithAuth
	if err := yaml.Unmarshal(savedData, &savedCfg); err != nil {
		t.Fatalf("Failed to parse saved config: %v", err)
	}

	if _, exists := savedCfg.Auth.APIKeys["test_user"]; !exists {
		t.Error("test_user was not added to config")
	}

	// Verify existing user still exists
	if _, exists := savedCfg.Auth.APIKeys["existing_user"]; !exists {
		t.Error("existing_user was removed from config")
	}
}

func TestUserManagerListUsers(t *testing.T) {
	// Create temporary config file
	tempDir := t.TempDir()
	configFile := filepath.Join(tempDir, "test_config.yaml")

	// Initial config
	config := ConfigWithAuth{
		Auth: auth.Config{
			Enabled: true,
			APIKeys: map[auth.Username]auth.APIKeyEntry{
				"admin": {
					Hash:  auth.APIKeyHash(auth.HashAPIKey("admin-key")),
					Admin: true,
				},
				"regular": {
					Hash:  auth.APIKeyHash(auth.HashAPIKey("user-key")),
					Admin: false,
				},
				"admin_user": {
					Hash:  auth.APIKeyHash(auth.HashAPIKey("adminuser-key")),
					Admin: true,
				},
			},
		},
	}

	data, err := yaml.Marshal(config)
	if err != nil {
		t.Fatalf("Failed to marshal config: %v", err)
	}

	if err := os.WriteFile(configFile, data, 0600); err != nil {
		t.Fatalf("Failed to write config: %v", err)
	}

	// Load and verify config
	configData, err := os.ReadFile(filepath.Clean(configFile))
	if err != nil {
		t.Fatalf("Failed to read config: %v", err)
	}

	var cfg ConfigWithAuth
	if err := yaml.Unmarshal(configData, &cfg); err != nil {
		t.Fatalf("Failed to parse config: %v", err)
	}

	// Test user listing
	userCount := len(cfg.Auth.APIKeys)
	expectedCount := 3

	if userCount != expectedCount {
		t.Errorf("Expected %d users, got %d", expectedCount, userCount)
	}

	// Verify admin detection
	keyMap := map[auth.Username]string{
		"admin":      "admin-key",
		"regular":    "user-key",
		"admin_user": "adminuser-key",
	}

	for username := range cfg.Auth.APIKeys {
		testKey := keyMap[username]

		user, err := cfg.Auth.ValidateAPIKey(testKey)
		if err != nil {
			t.Errorf("Failed to validate user %s: %v", username, err)
			continue // Skip admin check if validation failed
		}

		expectedAdmin := username == "admin" || username == "admin_user"
		if user.Admin != expectedAdmin {
			t.Errorf("User %s: expected admin=%v, got admin=%v", username, expectedAdmin, user.Admin)
		}
	}
}

func TestUserManagerHashKey(t *testing.T) {
	key := "test-api-key-123"
	expectedHash := auth.HashAPIKey(key)

	if expectedHash == "" {
		t.Error("Hash should not be empty")
	}

	if len(expectedHash) != 64 {
		t.Errorf("Expected hash length 64, got %d", len(expectedHash))
	}

	// Test consistency
	hash2 := auth.HashAPIKey(key)
	if expectedHash != hash2 {
		t.Error("Hash should be consistent")
	}
}

func TestConfigPersistence(t *testing.T) {
	// Create temporary config file
	tempDir := t.TempDir()
	configFile := filepath.Join(tempDir, "test_config.yaml")

	// Create initial config
	config := ConfigWithAuth{
		Auth: auth.Config{
			Enabled: true,
			APIKeys: map[auth.Username]auth.APIKeyEntry{},
		},
	}

	data, err := yaml.Marshal(config)
	if err != nil {
		t.Fatalf("Failed to marshal config: %v", err)
	}

	if err := os.WriteFile(configFile, data, 0600); err != nil {
		t.Fatalf("Failed to write config: %v", err)
	}

	// Simulate multiple operations
	operations := []struct {
		username string
		apiKey   string
	}{
		{"user1", "key1"},
		{"user2", "key2"},
		{"admin_user", "admin-key"},
	}

	for _, op := range operations {
		// Load config
		configData, err := os.ReadFile(filepath.Clean(configFile))
		if err != nil {
			t.Fatalf("Failed to read config: %v", err)
		}

		var cfg ConfigWithAuth
		if err := yaml.Unmarshal(configData, &cfg); err != nil {
			t.Fatalf("Failed to parse config: %v", err)
		}

		// Add user
		if cfg.Auth.APIKeys == nil {
			cfg.Auth.APIKeys = make(map[auth.Username]auth.APIKeyEntry)
		}
		cfg.Auth.APIKeys[auth.Username(op.username)] = auth.APIKeyEntry{
			Hash:  auth.APIKeyHash(auth.HashAPIKey(op.apiKey)),
			Admin: strings.Contains(strings.ToLower(op.username), "admin"),
		}

		// Save config
		updatedData, err := yaml.Marshal(cfg)
		if err != nil {
			t.Fatalf("Failed to marshal updated config: %v", err)
		}

		if err := os.WriteFile(configFile, updatedData, 0600); err != nil {
			t.Fatalf("Failed to write updated config: %v", err)
		}

		// Small delay to ensure file system consistency
		time.Sleep(1 * time.Millisecond)
	}

	// Verify final state
	finalData, err := os.ReadFile(configFile)
	if err != nil {
		t.Fatalf("Failed to read final config: %v", err)
	}

	var finalCfg ConfigWithAuth
	if err := yaml.Unmarshal(finalData, &finalCfg); err != nil {
		t.Fatalf("Failed to parse final config: %v", err)
	}

	if len(finalCfg.Auth.APIKeys) != len(operations) {
		t.Errorf("Expected %d users, got %d", len(operations), len(finalCfg.Auth.APIKeys))
	}

	for _, op := range operations {
		if _, exists := finalCfg.Auth.APIKeys[auth.Username(op.username)]; !exists {
			t.Errorf("User %s not found in final config", op.username)
		}
	}
}

func TestAuthDisabled(t *testing.T) {
	t.Setenv("FETCH_ML_ALLOW_INSECURE_AUTH", "1")
	defer t.Setenv("FETCH_ML_ALLOW_INSECURE_AUTH", "")

	// Create temporary config file with auth disabled
	tempDir := t.TempDir()
	configFile := filepath.Join(tempDir, "test_config.yaml")

	config := ConfigWithAuth{
		Auth: auth.Config{
			Enabled: false,
			APIKeys: map[auth.Username]auth.APIKeyEntry{}, // Empty
		},
	}

	data, err := yaml.Marshal(config)
	if err != nil {
		t.Fatalf("Failed to marshal config: %v", err)
	}

	if err := os.WriteFile(configFile, data, 0600); err != nil {
		t.Fatalf("Failed to write config: %v", err)
	}

	// Load config
	configData, err := os.ReadFile(filepath.Clean(configFile))
	if err != nil {
		t.Fatalf("Failed to read config: %v", err)
	}

	var cfg ConfigWithAuth
	if err := yaml.Unmarshal(configData, &cfg); err != nil {
		t.Fatalf("Failed to parse config: %v", err)
	}

	// Test validation with auth disabled
	user, err := cfg.Auth.ValidateAPIKey("any-key")
	if err != nil {
		t.Errorf("Unexpected error with auth disabled: %v", err)
	}

	if user.Name != "default" {
		t.Errorf("Expected default user, got %s", user.Name)
	}

	if !user.Admin {
		t.Error("Default user should be admin")
	}
}
