package handlers

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

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

type wave4CaddyManager struct {
	calls int
	err   error
}

func (m *wave4CaddyManager) ApplyConfig(context.Context) error {
	m.calls++
	return m.err
}

type wave4CacheInvalidator struct {
	calls int
}

func (i *wave4CacheInvalidator) InvalidateCache() {
	i.calls++
}

func registerCreatePermissionDeniedHook(t *testing.T, db *gorm.DB, name string, shouldFail func(*gorm.DB) bool) {
	t.Helper()
	require.NoError(t, db.Callback().Create().Before("gorm:create").Register(name, func(tx *gorm.DB) {
		if shouldFail(tx) {
			_ = tx.AddError(fmt.Errorf("permission denied"))
		}
	}))
	t.Cleanup(func() {
		_ = db.Callback().Create().Remove(name)
	})
}

func settingKeyFromCreateCallback(tx *gorm.DB) string {
	if tx == nil || tx.Statement == nil || tx.Statement.Dest == nil {
		return ""
	}
	switch v := tx.Statement.Dest.(type) {
	case *models.Setting:
		return v.Key
	case models.Setting:
		return v.Key
	default:
		return ""
	}
}

func attachDeterministicSecurityService(t *testing.T, h *SettingsHandler, db *gorm.DB) {
	t.Helper()

	securitySvc := services.NewSecurityService(db)
	h.SecuritySvc = securitySvc

	t.Cleanup(func() {
		securitySvc.Flush()
		securitySvc.Close()
	})
}

func performUpdateSettingRequest(t *testing.T, h *SettingsHandler, payload map[string]any) *httptest.ResponseRecorder {
	t.Helper()
	g := gin.New()
	g.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1))
		c.Next()
	})
	g.POST("/settings", h.UpdateSetting)

	body, err := json.Marshal(payload)
	require.NoError(t, err)

	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	g.ServeHTTP(w, req)
	return w
}

func performPatchConfigRequest(t *testing.T, h *SettingsHandler, payload map[string]any) *httptest.ResponseRecorder {
	t.Helper()
	g := gin.New()
	g.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1))
		c.Next()
	})
	g.PATCH("/config", h.PatchConfig)

	body, err := json.Marshal(payload)
	require.NoError(t, err)

	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPatch, "/config", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	g.ServeHTTP(w, req)
	return w
}

func TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors(t *testing.T) {
	t.Run("feature cerberus upsert permission denied", func(t *testing.T) {
		db := setupSettingsWave3DB(t)
		registerCreatePermissionDeniedHook(t, db, "wave4-deny-feature-cerberus", func(tx *gorm.DB) bool {
			return settingKeyFromCreateCallback(tx) == "feature.cerberus.enabled"
		})

		h := NewSettingsHandler(db)
		attachDeterministicSecurityService(t, h, db)
		h.DataRoot = "/app/data"

		w := performUpdateSettingRequest(t, h, map[string]any{
			"key":   "security.acl.enabled",
			"value": "true",
		})

		require.Equal(t, http.StatusInternalServerError, w.Code)
		require.Contains(t, w.Body.String(), "permissions_write_denied")
	})

}

func TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath(t *testing.T) {
	db := setupSettingsWave3DB(t)
	mgr := &wave4CaddyManager{}
	inv := &wave4CacheInvalidator{}

	h := NewSettingsHandlerWithDeps(db, mgr, inv, nil, "")
	w := performPatchConfigRequest(t, h, map[string]any{
		"security": map[string]any{
			"waf": map[string]any{"enabled": true},
		},
	})

	require.Equal(t, http.StatusOK, w.Code)
	require.Equal(t, 1, mgr.calls)
	require.Equal(t, 1, inv.calls)
}

func TestSettingsHandlerWave4_UpdateSetting_GenericSaveError(t *testing.T) {
	db := setupSettingsWave3DB(t)
	require.NoError(t, db.Callback().Create().Before("gorm:create").Register("wave4-generic-save-error", func(tx *gorm.DB) {
		if settingKeyFromCreateCallback(tx) == "security.waf.enabled" {
			_ = tx.AddError(fmt.Errorf("boom"))
		}
	}))
	t.Cleanup(func() {
		_ = db.Callback().Create().Remove("wave4-generic-save-error")
	})

	h := NewSettingsHandler(db)
	attachDeterministicSecurityService(t, h, db)
	h.DataRoot = "/app/data"

	w := performUpdateSettingRequest(t, h, map[string]any{
		"key":   "security.waf.enabled",
		"value": "true",
	})

	require.Equal(t, http.StatusInternalServerError, w.Code)
	require.Contains(t, w.Body.String(), "Failed to save setting")
}

func TestSettingsHandlerWave4_PatchConfig_InvalidAdminWhitelistFromSync(t *testing.T) {
	db := setupSettingsWave3DB(t)
	h := NewSettingsHandler(db)
	attachDeterministicSecurityService(t, h, db)
	h.DataRoot = "/app/data"

	w := performPatchConfigRequest(t, h, map[string]any{
		"security": map[string]any{
			"admin_whitelist": "10.10.10.10/",
		},
	})

	require.Equal(t, http.StatusBadRequest, w.Code)
	require.Contains(t, w.Body.String(), "Invalid admin_whitelist")
}

func TestSettingsHandlerWave4_TestPublicURL_BindError(t *testing.T) {
	db := setupSettingsWave3DB(t)
	h := NewSettingsHandler(db)

	g := gin.New()
	g.Use(func(c *gin.Context) {
		c.Set("role", "admin")
		c.Set("userID", uint(1))
		c.Next()
	})
	g.POST("/settings/test-public-url", h.TestPublicURL)

	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/settings/test-public-url", bytes.NewBufferString("{"))
	req.Header.Set("Content-Type", "application/json")
	g.ServeHTTP(w, req)

	require.Equal(t, http.StatusBadRequest, w.Code)
}
