package server

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"

	"warpbox/lib/config"
)

func TestAdminSettingsRequiresAuth(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodGet, "/admin/settings", nil)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusSeeOther {
		t.Fatalf("expected redirect, got %d", response.Code)
	}
	if location := response.Header().Get("Location"); location != "/admin/login" {
		t.Fatalf("expected login redirect, got %q", location)
	}
	if app == nil {
		t.Fatal("expected app setup")
	}
}

func TestAdminSettingsPageRenders(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodGet, "/admin/settings", nil)
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", response.Code)
	}
	body := response.Body.String()
	if !strings.Contains(body, "WarpBox Settings") {
		t.Fatalf("expected settings page title, got %s", body)
	}
	if !strings.Contains(body, "WARPBOX_API_ENABLED") {
		t.Fatalf("expected API env var in page body")
	}
}

func TestAdminSettingsExportIncludesCurrentValues(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodGet, "/admin/settings/export", nil)
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", response.Code)
	}

	var payload struct {
		Format   string            `json:"format"`
		Settings map[string]string `json:"settings"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal returned error: %v", err)
	}
	if payload.Format != "warpbox.settings.export.v1" {
		t.Fatalf("unexpected export format: %q", payload.Format)
	}
	if payload.Settings[config.SettingAPIEnabled] != "false" {
		t.Fatalf("expected api_enabled to reflect environment false, got %q", payload.Settings[config.SettingAPIEnabled])
	}
}

func TestAdminSettingsSavePersistsEditableOverrides(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodPost, "/admin/settings/save", strings.NewReader(`{"values":{"api_enabled":"true","box_poll_interval_ms":"6000","global_max_file_size_gb":"0.5"}}`))
	request.Header.Set("Content-Type", "application/json")
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", response.Code, response.Body.String())
	}
	if !app.config.APIEnabled {
		t.Fatal("expected APIEnabled override to be applied")
	}
	if app.config.BoxPollIntervalMS != 6000 {
		t.Fatalf("expected poll interval override, got %d", app.config.BoxPollIntervalMS)
	}
	if app.config.GlobalMaxFileSizeBytes != 512*1024*1024 {
		t.Fatalf("expected size override in bytes, got %d", app.config.GlobalMaxFileSizeBytes)
	}

	overrides, err := config.ReadAdminSettingsOverrides(app.settingsOverridesPath)
	if err != nil {
		t.Fatalf("ReadAdminSettingsOverrides returned error: %v", err)
	}
	if overrides[config.SettingAPIEnabled] != "true" {
		t.Fatalf("expected persisted API override, got %#v", overrides)
	}
	if _, exists := overrides[config.SettingBoxPollIntervalMS]; !exists {
		t.Fatalf("expected changed poll interval override to be persisted, got %#v", overrides)
	}
	if _, exists := overrides[config.SettingSessionTTLSeconds]; exists {
		t.Fatalf("expected untouched setting to stay out of overrides, got %#v", overrides)
	}
}

func TestAdminSettingsSaveRejectsLockedSetting(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodPost, "/admin/settings/save", strings.NewReader(`{"values":{"data_dir":"./other"}}`))
	request.Header.Set("Content-Type", "application/json")
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", response.Code)
	}
}

func TestAdminSettingsImportSkipsLockedAndUnknownKeys(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodPost, "/admin/settings/import", strings.NewReader(`{"settings":{"api_enabled":"true","data_dir":"./other","bogus":"x"}}`))
	request.Header.Set("Content-Type", "application/json")
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", response.Code, response.Body.String())
	}
	if !app.config.APIEnabled {
		t.Fatal("expected editable import value to apply")
	}

	var payload struct {
		Warnings []string `json:"warnings"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal returned error: %v", err)
	}
	if len(payload.Warnings) != 2 {
		t.Fatalf("expected 2 warnings, got %#v", payload.Warnings)
	}
}

func TestAdminSettingsResetUsesBuiltInDefaults(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodPost, "/admin/settings/reset", strings.NewReader(`{}`))
	request.Header.Set("Content-Type", "application/json")
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", response.Code, response.Body.String())
	}
	if app.config.APIEnabled {
		t.Fatal("expected reset to respect environment and restore APIEnabled=false")
	}
}

func setupAdminSettingsTest(t *testing.T) (*App, *gin.Engine) {
	t.Helper()
	gin.SetMode(gin.TestMode)
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("Getwd returned error: %v", err)
	}
	root := filepath.Clean(filepath.Join(cwd, "..", ".."))
	if err := os.Chdir(root); err != nil {
		t.Fatalf("Chdir returned error: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(cwd)
	})
	clearAdminSettingsEnv(t)
	t.Setenv("WARPBOX_DATA_DIR", t.TempDir())
	t.Setenv("WARPBOX_ADMIN_PASSWORD", "secret")
	t.Setenv("WARPBOX_ADMIN_ENABLED", "true")
	t.Setenv("WARPBOX_API_ENABLED", "false")

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	if err := cfg.EnsureDirectories(); err != nil {
		t.Fatalf("EnsureDirectories returned error: %v", err)
	}

	app := &App{
		config:                cfg,
		settingsOverridesPath: filepath.Join(cfg.DBDir, config.AdminSettingsOverrideFilename),
	}

	htmlTemplates, err := loadHTMLTemplates()
	if err != nil {
		t.Fatalf("loadHTMLTemplates returned error: %v", err)
	}

	router := gin.New()
	router.SetHTMLTemplate(htmlTemplates)
	admin := router.Group("/admin")
	admin.GET("/login", app.handleAdminLogin)
	protected := router.Group("/admin", app.adminAuthMiddleware)
	protected.GET("/settings", app.handleAdminSettings)
	protected.GET("/settings/export", app.handleAdminSettingsExport)
	protected.POST("/settings/save", app.handleAdminSettingsSave)
	protected.POST("/settings/import", app.handleAdminSettingsImport)
	protected.POST("/settings/reset", app.handleAdminSettingsReset)
	return app, router
}

func authCookie(app *App) *http.Cookie {
	return &http.Cookie{Name: adminSessionCookie, Value: app.adminSessionToken()}
}

func clearAdminSettingsEnv(t *testing.T) {
	t.Helper()
	for _, name := range []string{
		"WARPBOX_DATA_DIR",
		"WARPBOX_ADMIN_PASSWORD",
		"WARPBOX_ADMIN_USERNAME",
		"WARPBOX_ADMIN_EMAIL",
		"WARPBOX_ADMIN_ENABLED",
		"WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE",
		"WARPBOX_ADMIN_COOKIE_SECURE",
		"WARPBOX_GUEST_UPLOADS_ENABLED",
		"WARPBOX_API_ENABLED",
		"WARPBOX_ZIP_DOWNLOADS_ENABLED",
		"WARPBOX_ONE_TIME_DOWNLOADS_ENABLED",
		"WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS",
		"WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE",
		"WARPBOX_RENEW_ON_ACCESS_ENABLED",
		"WARPBOX_RENEW_ON_DOWNLOAD_ENABLED",
		"WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS",
		"WARPBOX_MAX_GUEST_EXPIRY_SECONDS",
		"WARPBOX_GLOBAL_MAX_FILE_SIZE_GB",
		"WARPBOX_GLOBAL_MAX_FILE_SIZE_MB",
		"WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES",
		"WARPBOX_GLOBAL_MAX_BOX_SIZE_GB",
		"WARPBOX_GLOBAL_MAX_BOX_SIZE_MB",
		"WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES",
		"WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_GB",
		"WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_MB",
		"WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES",
		"WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_GB",
		"WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_MB",
		"WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES",
		"WARPBOX_SESSION_TTL_SECONDS",
		"WARPBOX_BOX_POLL_INTERVAL_MS",
		"WARPBOX_THUMBNAIL_BATCH_SIZE",
		"WARPBOX_THUMBNAIL_INTERVAL_SECONDS",
		"WARPBOX_SECURITY_IP_WHITELIST",
		"WARPBOX_SECURITY_ADMIN_IP_WHITELIST",
		"WARPBOX_TRUSTED_PROXY_CIDRS",
		"WARPBOX_SECURITY_LOGIN_WINDOW_SECONDS",
		"WARPBOX_SECURITY_LOGIN_MAX_ATTEMPTS",
		"WARPBOX_SECURITY_BAN_SECONDS",
		"WARPBOX_SECURITY_SCAN_WINDOW_SECONDS",
		"WARPBOX_SECURITY_SCAN_MAX_ATTEMPTS",
		"WARPBOX_SECURITY_UPLOAD_WINDOW_SECONDS",
		"WARPBOX_SECURITY_UPLOAD_MAX_REQUESTS",
		"WARPBOX_SECURITY_UPLOAD_MAX_GB",
		"WARPBOX_SECURITY_UPLOAD_MAX_MB",
		"WARPBOX_SECURITY_UPLOAD_MAX_BYTES",
	} {
		t.Setenv(name, "")
	}
}

func TestAdminSettingsSaveRejectsInvalidTrustedProxyCIDR(t *testing.T) {
	app, router := setupAdminSettingsTest(t)

	request := httptest.NewRequest(http.MethodPost, "/admin/settings/save", strings.NewReader(`{"values":{"trusted_proxy_cidrs":"not-a-cidr"}}`))
	request.Header.Set("Content-Type", "application/json")
	request.AddCookie(authCookie(app))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	if response.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", response.Code)
	}
}
