package server

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/config"
	"github.com/Wikid82/charon/backend/internal/database"
	"github.com/Wikid82/charon/backend/internal/models"
)

// setupTestDB creates a temporary test database
func setupTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	// Create temp database file
	tmpFile := t.TempDir() + "/test.db"
	db, err := database.Connect(tmpFile)
	require.NoError(t, err, "Failed to create test database")

	// Run migrations
	err = db.AutoMigrate(
		&models.Setting{},
		&models.SecurityConfig{},
		&models.SecurityAudit{},
	)
	require.NoError(t, err, "Failed to run migrations")

	return db
}

func TestEmergencyServer_Disabled(t *testing.T) {
	db := setupTestDB(t)

	cfg := config.EmergencyConfig{
		Enabled: false,
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully when disabled")

	// Server should not be running
	assert.Nil(t, server.server, "HTTP server should not be initialized when disabled")
}

func TestEmergencyServer_Health(t *testing.T) {
	db := setupTestDB(t)

	// Set emergency token required for enabled server
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", "test-token-for-health-check-32chars"))
	defer func() { _ = os.Unsetenv("CHARON_EMERGENCY_TOKEN") }()

	cfg := config.EmergencyConfig{
		Enabled:     true,
		BindAddress: "127.0.0.1:0", // Random port for testing
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully")
	defer func() { _ = server.Stop(context.Background()) }()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	// Get actual port
	addr := server.GetAddr()
	assert.NotEmpty(t, addr, "Server address should be set")

	// Make health check request
	resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
	require.NoError(t, err, "Health check request should succeed")
	defer func() { _ = resp.Body.Close() }()

	assert.Equal(t, http.StatusOK, resp.StatusCode, "Health check should return 200")

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(t, err, "Should decode JSON response")

	assert.Equal(t, "ok", body["status"], "Status should be ok")
	assert.Equal(t, "emergency", body["server"], "Server should be emergency")
	assert.NotEmpty(t, body["time"], "Time should be present")
}

func TestEmergencyServer_SecurityReset(t *testing.T) {
	db := setupTestDB(t)

	// Set emergency token
	emergencyToken := "test-emergency-token-for-testing-32chars"
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", emergencyToken))
	defer func() { require.NoError(t, os.Unsetenv("CHARON_EMERGENCY_TOKEN")) }()

	cfg := config.EmergencyConfig{
		Enabled:     true,
		BindAddress: "127.0.0.1:0",
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully")
	defer func() { _ = server.Stop(context.Background()) }()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	addr := server.GetAddr()

	// Create HTTP client
	client := &http.Client{}

	// Make emergency reset request
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/emergency/security-reset", addr), nil)
	require.NoError(t, err, "Should create request")
	req.Header.Set("X-Emergency-Token", emergencyToken)

	resp, err := client.Do(req)
	require.NoError(t, err, "Emergency reset request should succeed")
	defer func() { _ = resp.Body.Close() }()

	assert.Equal(t, http.StatusOK, resp.StatusCode, "Emergency reset should return 200")

	var body map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&body)
	require.NoError(t, err, "Should decode JSON response")

	assert.True(t, body["success"].(bool), "Success should be true")
	assert.NotNil(t, body["disabled_modules"], "Disabled modules should be present")
}

func TestEmergencyServer_BasicAuth(t *testing.T) {
	db := setupTestDB(t)

	// Set emergency token
	emergencyToken := "test-emergency-token-for-testing-32chars"
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", emergencyToken))
	defer func() { require.NoError(t, os.Unsetenv("CHARON_EMERGENCY_TOKEN")) }()

	cfg := config.EmergencyConfig{
		Enabled:           true,
		BindAddress:       "127.0.0.1:0",
		BasicAuthUsername: "admin",
		BasicAuthPassword: "testpass",
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully")
	defer func() { _ = server.Stop(context.Background()) }()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	addr := server.GetAddr()

	t.Run("WithoutAuth", func(t *testing.T) {
		// Try without Basic Auth - should fail
		req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/emergency/security-reset", addr), nil)
		require.NoError(t, err, "Should create request")
		req.Header.Set("X-Emergency-Token", emergencyToken)

		client := &http.Client{}
		resp, err := client.Do(req)
		require.NoError(t, err, "Request should complete")
		defer func() { _ = resp.Body.Close() }()

		assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "Should require authentication")
	})

	t.Run("WithInvalidAuth", func(t *testing.T) {
		// Try with wrong credentials
		req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/emergency/security-reset", addr), nil)
		require.NoError(t, err, "Should create request")
		req.Header.Set("X-Emergency-Token", emergencyToken)
		req.SetBasicAuth("admin", "wrongpassword")

		client := &http.Client{}
		resp, err := client.Do(req)
		require.NoError(t, err, "Request should complete")
		defer func() { _ = resp.Body.Close() }()

		assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "Should reject invalid credentials")
	})

	t.Run("WithValidAuth", func(t *testing.T) {
		// Try with correct credentials
		req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/emergency/security-reset", addr), nil)
		require.NoError(t, err, "Should create request")
		req.Header.Set("X-Emergency-Token", emergencyToken)
		req.SetBasicAuth("admin", "testpass")

		client := &http.Client{}
		resp, err := client.Do(req)
		require.NoError(t, err, "Request should complete")
		defer func() { _ = resp.Body.Close() }()

		assert.Equal(t, http.StatusOK, resp.StatusCode, "Should accept valid credentials")

		var body map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&body)
		require.NoError(t, err, "Should decode JSON response")

		assert.True(t, body["success"].(bool), "Success should be true")
	})
}

func TestEmergencyServer_NoAuth_Warning(t *testing.T) {
	// This test verifies that a warning is logged when no auth is configured
	// We can't easily test log output, but we can verify the server starts
	db := setupTestDB(t)

	// Set emergency token required for enabled server
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", "test-token-for-no-auth-warning-test"))
	defer func() { _ = os.Unsetenv("CHARON_EMERGENCY_TOKEN") }()

	cfg := config.EmergencyConfig{
		Enabled:     true,
		BindAddress: "127.0.0.1:0",
		// No auth configured
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start even without auth")
	defer func() { _ = server.Stop(context.Background()) }()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	// Verify server is accessible without auth
	addr := server.GetAddr()
	resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
	require.NoError(t, err, "Health check should work without auth")
	defer func() { _ = resp.Body.Close() }()

	assert.Equal(t, http.StatusOK, resp.StatusCode, "Should return 200")
}

func TestEmergencyServer_GracefulShutdown(t *testing.T) {
	db := setupTestDB(t)

	// Set emergency token required for enabled server
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", "test-token-for-graceful-shutdown-test"))
	defer func() { _ = os.Unsetenv("CHARON_EMERGENCY_TOKEN") }()

	cfg := config.EmergencyConfig{
		Enabled:     true,
		BindAddress: "127.0.0.1:0",
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully")

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	// Verify server is running
	addr := server.GetAddr()
	resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
	require.NoError(t, err, "Server should be running")
	_ = resp.Body.Close()

	// Stop server with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	err = server.Stop(ctx)
	assert.NoError(t, err, "Server should stop gracefully")

	// Verify server is stopped (request should fail)
	resp, err = http.Get(fmt.Sprintf("http://%s/health", addr))
	if resp != nil {
		_ = resp.Body.Close()
	}
	assert.Error(t, err, "Server should be stopped")
}

func TestEmergencyServer_MultipleEndpoints(t *testing.T) {
	db := setupTestDB(t)

	// Set emergency token
	emergencyToken := "test-emergency-token-for-testing-32chars"
	require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", emergencyToken))
	defer func() { require.NoError(t, os.Unsetenv("CHARON_EMERGENCY_TOKEN")) }()

	cfg := config.EmergencyConfig{
		Enabled:     true,
		BindAddress: "127.0.0.1:0",
	}

	server := NewEmergencyServer(db, cfg)
	err := server.Start()
	require.NoError(t, err, "Server should start successfully")
	defer func() { _ = server.Stop(context.Background()) }()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	addr := server.GetAddr()

	t.Run("HealthEndpoint", func(t *testing.T) {
		resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
		require.NoError(t, err)
		defer func() { _ = resp.Body.Close() }()
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("EmergencyResetEndpoint", func(t *testing.T) {
		req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/emergency/security-reset", addr), nil)
		require.NoError(t, err)
		req.Header.Set("X-Emergency-Token", emergencyToken)

		client := &http.Client{}
		resp, err := client.Do(req)
		require.NoError(t, err)
		defer func() { _ = resp.Body.Close() }()
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("NotFoundEndpoint", func(t *testing.T) {
		resp, err := http.Get(fmt.Sprintf("http://%s/nonexistent", addr))
		require.NoError(t, err)
		defer func() { _ = resp.Body.Close() }()
		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
	})
}

// TestEmergencyServer_StartupValidation tests that server fails fast if token is empty or whitespace
func TestEmergencyServer_StartupValidation(t *testing.T) {
	db := setupTestDB(t)

	tests := []struct {
		name          string
		token         string
		expectSuccess bool
		description   string
	}{
		{
			name:          "EmptyToken",
			token:         "",
			expectSuccess: false,
			description:   "Server should fail to start with empty token",
		},
		{
			name:          "WhitespaceToken",
			token:         "   ",
			expectSuccess: false,
			description:   "Server should fail to start with whitespace-only token",
		},
		{
			name:          "ValidToken",
			token:         "test-emergency-token-for-testing-32chars",
			expectSuccess: true,
			description:   "Server should start successfully with valid token",
		},
		{
			name:          "ShortToken",
			token:         "short",
			expectSuccess: true, // Server starts but logs warning
			description:   "Server should start with short token but log warning",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Set token
			if tt.token != "" {
				require.NoError(t, os.Setenv("CHARON_EMERGENCY_TOKEN", tt.token))
			} else {
				_ = os.Unsetenv("CHARON_EMERGENCY_TOKEN")
			}
			defer func() { _ = os.Unsetenv("CHARON_EMERGENCY_TOKEN") }()

			cfg := config.EmergencyConfig{
				Enabled:     true,
				BindAddress: "127.0.0.1:0",
			}

			server := NewEmergencyServer(db, cfg)
			err := server.Start()

			if tt.expectSuccess {
				assert.NoError(t, err, tt.description)
				if err == nil {
					_ = server.Stop(context.Background())
				}
			} else {
				assert.Error(t, err, tt.description)
			}
		})
	}
}

// TestEmergencyServer_TokenRedaction tests the token redaction function
func TestEmergencyServer_TokenRedaction(t *testing.T) {
	tests := []struct {
		name     string
		token    string
		expected string
	}{
		{
			name:     "EmptyToken",
			token:    "",
			expected: "[EMERGENCY_TOKEN:empty]",
		},
		{
			name:     "ShortToken",
			token:    "short",
			expected: "[EMERGENCY_TOKEN:***]",
		},
		{
			name:     "ValidToken",
			token:    "f51dedd6a4f2eaa200dcbf4feecae78ff926e06d9094d726f3613729b66d346b",
			expected: "[EMERGENCY_TOKEN:f51d...346b]",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := redactToken(tt.token)
			assert.Equal(t, tt.expected, result)
		})
	}
}
