package handlers

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"
)

// TestStartSyncsSettingsTable verifies that Start() updates the settings table.
func TestStartSyncsSettingsTable(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	// Migrate both SecurityConfig and Setting tables
	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	tmpDir := t.TempDir()
	fe := &fakeExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// Verify settings table is initially empty
	var initialSetting models.Setting
	err := db.Where("key = ?", "security.crowdsec.enabled").First(&initialSetting).Error
	require.Error(t, err, "expected setting to not exist initially")

	// Start CrowdSec
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	// Verify setting was created/updated to "true"
	var setting models.Setting
	err = db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
	require.NoError(t, err, "expected setting to be created after Start")
	require.Equal(t, "true", setting.Value)
	require.Equal(t, "security", setting.Category)
	require.Equal(t, "bool", setting.Type)

	// Also verify SecurityConfig was updated
	var cfg models.SecurityConfig
	err = db.First(&cfg).Error
	require.NoError(t, err, "expected SecurityConfig to exist")
	require.Equal(t, "local", cfg.CrowdSecMode)
	require.True(t, cfg.Enabled)
}

// TestStopSyncsSettingsTable verifies that Stop() updates the settings table.
func TestStopSyncsSettingsTable(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	// Migrate both SecurityConfig and Setting tables
	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	tmpDir := t.TempDir()
	fe := &fakeExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// First start CrowdSec to create the settings
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	// Verify setting is "true" after start
	var settingAfterStart models.Setting
	err := db.Where("key = ?", "security.crowdsec.enabled").First(&settingAfterStart).Error
	require.NoError(t, err)
	require.Equal(t, "true", settingAfterStart.Value)

	// Now stop CrowdSec
	w2 := httptest.NewRecorder()
	req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody)
	r.ServeHTTP(w2, req2)
	require.Equal(t, http.StatusOK, w2.Code)

	// Verify setting was updated to "false"
	var settingAfterStop models.Setting
	err = db.Where("key = ?", "security.crowdsec.enabled").First(&settingAfterStop).Error
	require.NoError(t, err)
	require.Equal(t, "false", settingAfterStop.Value)

	// Also verify SecurityConfig was updated
	var cfg models.SecurityConfig
	err = db.First(&cfg).Error
	require.NoError(t, err)
	require.Equal(t, "disabled", cfg.CrowdSecMode)
	require.False(t, cfg.Enabled)
}

// TestStartAndStopStateConsistency verifies consistent state across Start/Stop cycles.
func TestStartAndStopStateConsistency(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	tmpDir := t.TempDir()
	fe := &fakeExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// Perform multiple start/stop cycles
	for i := 0; i < 3; i++ {
		// Start
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
		r.ServeHTTP(w, req)
		require.Equal(t, http.StatusOK, w.Code, "cycle %d start", i)

		// Verify both tables are in sync
		var setting models.Setting
		err := db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
		require.NoError(t, err, "cycle %d: setting should exist after start", i)
		require.Equal(t, "true", setting.Value, "cycle %d: setting should be true after start", i)

		var cfg models.SecurityConfig
		err = db.First(&cfg).Error
		require.NoError(t, err, "cycle %d: config should exist after start", i)
		require.Equal(t, "local", cfg.CrowdSecMode, "cycle %d: mode should be local after start", i)

		// Stop
		w2 := httptest.NewRecorder()
		req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody)
		r.ServeHTTP(w2, req2)
		require.Equal(t, http.StatusOK, w2.Code, "cycle %d stop", i)

		// Verify both tables are in sync
		err = db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
		require.NoError(t, err, "cycle %d: setting should exist after stop", i)
		require.Equal(t, "false", setting.Value, "cycle %d: setting should be false after stop", i)

		err = db.First(&cfg).Error
		require.NoError(t, err, "cycle %d: config should exist after stop", i)
		require.Equal(t, "disabled", cfg.CrowdSecMode, "cycle %d: mode should be disabled after stop", i)
	}
}

// TestExistingSettingIsUpdated verifies that an existing setting is updated, not duplicated.
func TestExistingSettingIsUpdated(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	// Pre-create a setting with a different value
	existingSetting := models.Setting{
		Key:      "security.crowdsec.enabled",
		Value:    "false",
		Category: "security",
		Type:     "bool",
	}
	require.NoError(t, db.Create(&existingSetting).Error)

	tmpDir := t.TempDir()
	fe := &fakeExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// Start CrowdSec
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	// Verify the existing setting was updated (not duplicated)
	var settings []models.Setting
	err := db.Where("key = ?", "security.crowdsec.enabled").Find(&settings).Error
	require.NoError(t, err)
	require.Len(t, settings, 1, "should not create duplicate settings")
	require.Equal(t, "true", settings[0].Value, "setting should be updated to true")
}

// fakeFailingExec simulates an executor that fails on Start.
type fakeFailingExec struct{}

func (f *fakeFailingExec) Start(ctx context.Context, binPath, configDir string) (int, error) {
	return 0, http.ErrAbortHandler
}

func (f *fakeFailingExec) Stop(ctx context.Context, configDir string) error {
	return nil
}

func (f *fakeFailingExec) Status(ctx context.Context, configDir string) (running bool, pid int, err error) {
	return false, 0, nil
}

// TestStartFailureRevertsSettings verifies that a failed Start reverts the settings.
func TestStartFailureRevertsSettings(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	tmpDir := t.TempDir()
	fe := &fakeFailingExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// Pre-create a setting with "false" to verify it's reverted
	existingSetting := models.Setting{
		Key:      "security.crowdsec.enabled",
		Value:    "false",
		Category: "security",
		Type:     "bool",
	}
	require.NoError(t, db.Create(&existingSetting).Error)

	// Try to start CrowdSec (this will fail)
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusInternalServerError, w.Code)

	// Verify the setting was reverted to "false"
	var setting models.Setting
	err := db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
	require.NoError(t, err)
	require.Equal(t, "false", setting.Value, "setting should be reverted to false on failure")
}

// TestStatusResponseFormat verifies the status endpoint response format.
func TestStatusResponseFormat(t *testing.T) {
	gin.SetMode(gin.TestMode)
	db := OpenTestDB(t)

	require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))

	tmpDir := t.TempDir()
	fe := &fakeExec{}
	h := NewCrowdsecHandler(db, fe, "/bin/false", tmpDir)

	r := gin.New()
	g := r.Group("/api/v1")
	h.RegisterRoutes(g)

	// Get status
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", http.NoBody)
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	var resp map[string]any
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	// Verify response contains expected fields
	require.Contains(t, resp, "running")
	require.Contains(t, resp, "pid")
	require.Contains(t, resp, "lapi_ready")
}
