//go:build testing

package config_test

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

	"github.com/henrygd/beszel/internal/tests"

	"github.com/henrygd/beszel/internal/hub/config"

	"github.com/pocketbase/pocketbase/core"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

// Config struct for testing (copied from config package since it's not exported)
type testConfig struct {
	Systems []testSystemConfig `yaml:"systems"`
}

type testSystemConfig struct {
	Name  string   `yaml:"name"`
	Host  string   `yaml:"host"`
	Port  uint16   `yaml:"port,omitempty"`
	Users []string `yaml:"users"`
	Token string   `yaml:"token,omitempty"`
}

// Helper function to create a test system for config tests
// func createConfigTestSystem(app core.App, name, host string, port uint16, userIDs []string) (*core.Record, error) {
// 	systemCollection, err := app.FindCollectionByNameOrId("systems")
// 	if err != nil {
// 		return nil, err
// 	}

// 	system := core.NewRecord(systemCollection)
// 	system.Set("name", name)
// 	system.Set("host", host)
// 	system.Set("port", port)
// 	system.Set("users", userIDs)
// 	system.Set("status", "pending")

// 	return system, app.Save(system)
// }

// Helper function to create a fingerprint record
func createConfigTestFingerprint(app core.App, systemID, token, fingerprint string) (*core.Record, error) {
	fingerprintCollection, err := app.FindCollectionByNameOrId("fingerprints")
	if err != nil {
		return nil, err
	}

	fp := core.NewRecord(fingerprintCollection)
	fp.Set("system", systemID)
	fp.Set("token", token)
	fp.Set("fingerprint", fingerprint)

	return fp, app.Save(fp)
}

// TestConfigSyncWithTokens tests the config.SyncSystems function with various token scenarios
func TestConfigSyncWithTokens(t *testing.T) {
	testHub, err := tests.NewTestHub()
	require.NoError(t, err)
	defer testHub.Cleanup()

	// Create test user
	user, err := tests.CreateUser(testHub.App, "admin@example.com", "testtesttest")
	require.NoError(t, err)

	testCases := []struct {
		name        string
		setupFunc   func() (string, *core.Record, *core.Record) // Returns: existing token, system record, fingerprint record
		configYAML  string
		expectToken string // Expected token after sync
		description string
	}{
		{
			name: "new system with token in config",
			setupFunc: func() (string, *core.Record, *core.Record) {
				return "", nil, nil // No existing system
			},
			configYAML: `systems:
  - name: "new-server"
    host: "new.example.com"
    port: 45876
    users:
      - "admin@example.com"
    token: "explicit-token-123"`,
			expectToken: "explicit-token-123",
			description: "New system should use token from config",
		},
		{
			name: "existing system without token in config (preserve existing)",
			setupFunc: func() (string, *core.Record, *core.Record) {
				// Create existing system and fingerprint
				system, err := tests.CreateRecord(testHub.App, "systems", map[string]any{
					"name":  "preserve-server",
					"host":  "preserve.example.com",
					"port":  45876,
					"users": []string{user.Id},
				})
				require.NoError(t, err)

				fingerprint, err := createConfigTestFingerprint(testHub.App, system.Id, "preserve-token-999", "preserve-fingerprint")
				require.NoError(t, err)

				return "preserve-token-999", system, fingerprint
			},
			configYAML: `systems:
  - name: "preserve-server"
    host: "preserve.example.com"
    port: 45876
    users:
      - "admin@example.com"`,
			expectToken: "preserve-token-999",
			description: "Existing system should preserve original token when config doesn't specify one",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Setup test data
			_, existingSystem, existingFingerprint := tc.setupFunc()

			// Write config file
			configPath := filepath.Join(testHub.DataDir(), "config.yml")
			err := os.WriteFile(configPath, []byte(tc.configYAML), 0644)
			require.NoError(t, err)

			// Create serve event and sync
			event := &core.ServeEvent{App: testHub.App}
			err = config.SyncSystems(event)
			require.NoError(t, err)

			// Parse the config to get the system name for verification
			var configData testConfig
			err = yaml.Unmarshal([]byte(tc.configYAML), &configData)
			require.NoError(t, err)
			require.Len(t, configData.Systems, 1)
			systemName := configData.Systems[0].Name

			// Find the system after sync
			systems, err := testHub.FindRecordsByFilter("systems", "name = {:name}", "", -1, 0, map[string]any{"name": systemName})
			require.NoError(t, err)
			require.Len(t, systems, 1)
			system := systems[0]

			// Find the fingerprint record
			fingerprints, err := testHub.FindRecordsByFilter("fingerprints", "system = {:system}", "", -1, 0, map[string]any{"system": system.Id})
			require.NoError(t, err)
			require.Len(t, fingerprints, 1)
			fingerprint := fingerprints[0]

			// Verify token
			actualToken := fingerprint.GetString("token")
			if tc.expectToken == "" {
				// For generated tokens, just verify it's not empty and is a valid UUID format
				assert.NotEmpty(t, actualToken, tc.description)
				assert.Len(t, actualToken, 36, "Generated token should be UUID format") // UUID length
			} else {
				assert.Equal(t, tc.expectToken, actualToken, tc.description)
			}

			// For existing systems, verify fingerprint is preserved
			if existingFingerprint != nil {
				actualFingerprint := fingerprint.GetString("fingerprint")
				expectedFingerprint := existingFingerprint.GetString("fingerprint")
				assert.Equal(t, expectedFingerprint, actualFingerprint, "Fingerprint should be preserved")
			}

			// Cleanup for next test
			if existingSystem != nil {
				testHub.Delete(existingSystem)
			}
			if existingFingerprint != nil {
				testHub.Delete(existingFingerprint)
			}
			// Clean up the new records
			testHub.Delete(system)
			testHub.Delete(fingerprint)
		})
	}
}

// TestConfigMigrationScenario tests the specific migration scenario mentioned in the discussion
func TestConfigMigrationScenario(t *testing.T) {
	testHub, err := tests.NewTestHub(t.TempDir())
	require.NoError(t, err)
	defer testHub.Cleanup()

	// Create test user
	user, err := tests.CreateUser(testHub.App, "admin@example.com", "testtesttest")
	require.NoError(t, err)

	// Simulate migration scenario: system exists with token from migration
	existingSystem, err := tests.CreateRecord(testHub.App, "systems", map[string]any{
		"name":  "migrated-server",
		"host":  "migrated.example.com",
		"port":  45876,
		"users": []string{user.Id},
	})
	require.NoError(t, err)

	migrationToken := "migration-generated-token-123"
	existingFingerprint, err := createConfigTestFingerprint(testHub.App, existingSystem.Id, migrationToken, "existing-fingerprint-from-agent")
	require.NoError(t, err)

	// User exports config BEFORE this update (so no token field in YAML)
	oldConfigYAML := `systems:
  - name: "migrated-server"
    host: "migrated.example.com"
    port: 45876
    users:
      - "admin@example.com"`

	// Write old config file and import
	configPath := filepath.Join(testHub.DataDir(), "config.yml")
	err = os.WriteFile(configPath, []byte(oldConfigYAML), 0644)
	require.NoError(t, err)

	event := &core.ServeEvent{App: testHub.App}
	err = config.SyncSystems(event)
	require.NoError(t, err)

	// Verify the original token is preserved
	updatedFingerprint, err := testHub.FindRecordById("fingerprints", existingFingerprint.Id)
	require.NoError(t, err)

	actualToken := updatedFingerprint.GetString("token")
	assert.Equal(t, migrationToken, actualToken, "Migration token should be preserved when config doesn't specify a token")

	// Verify fingerprint is also preserved
	actualFingerprint := updatedFingerprint.GetString("fingerprint")
	assert.Equal(t, "existing-fingerprint-from-agent", actualFingerprint, "Existing fingerprint should be preserved")

	// Verify system still exists and is updated correctly
	updatedSystem, err := testHub.FindRecordById("systems", existingSystem.Id)
	require.NoError(t, err)
	assert.Equal(t, "migrated-server", updatedSystem.GetString("name"))
	assert.Equal(t, "migrated.example.com", updatedSystem.GetString("host"))
}
