package setting

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"system-altrak/internal/domain"

	"github.com/gofiber/fiber/v2"
)

type settingServiceMock struct {
	list       []domain.SystemSetting
	listErr    error
	setErr     error
	setErrKey  string
	setCalls   map[string]string
	getMap     map[string]string
	getDefault string
	getErr     error
}

func (m *settingServiceMock) Get(key string) (string, error) {
	if m.getErr != nil {
		return "", m.getErr
	}
	if m.getMap != nil {
		if val, ok := m.getMap[key]; ok {
			return val, nil
		}
	}
	return m.getDefault, nil
}

func (m *settingServiceMock) Set(key, value string) error {
	if m.setCalls == nil {
		m.setCalls = make(map[string]string)
	}
	m.setCalls[key] = value
	if m.setErr != nil && (m.setErrKey == "" || m.setErrKey == key) {
		return m.setErr
	}
	return nil
}

func (m *settingServiceMock) List() ([]domain.SystemSetting, error) {
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.list, nil
}

func (m *settingServiceMock) ListPermissions() ([]domain.RolePermission, error) {
	return nil, nil
}

func (m *settingServiceMock) UpdatePermission(role, module string, canView, canCreate, canEdit, canDelete, canExport bool) error {
	return nil
}

func (m *settingServiceMock) GetStats() (map[string]interface{}, error) {
	return nil, nil
}

func (m *settingServiceMock) OptimizeDB() error {
	return nil
}

func buildSettingHandlerTestApp(h *Handler) *fiber.App {
	app := fiber.New()
	app.Get("/settings", h.GetSettings)
	app.Post("/settings", h.UpdateSettings)
	return app
}

func TestGetSettingsReturnsMappedSettings(t *testing.T) {
	svc := &settingServiceMock{
		list: []domain.SystemSetting{
			{Key: "cl_format", Value: "{INC}/CL/{YY}"},
			{Key: "timezone", Value: "Asia/Jakarta"},
		},
	}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/settings", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}

	var body struct {
		Success bool              `json:"success"`
		Message string            `json:"message"`
		Data    map[string]string `json:"data"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	if !body.Success {
		t.Fatal("expected success=true")
	}
	if body.Message != "Settings retrieved" {
		t.Fatalf("unexpected message: %q", body.Message)
	}
	if body.Data["cl_format"] != "{INC}/CL/{YY}" || body.Data["timezone"] != "Asia/Jakarta" {
		t.Fatalf("unexpected settings map payload: %+v", body.Data)
	}
}

func TestGetSettingsReturnsInternalErrorWhenServiceFails(t *testing.T) {
	svc := &settingServiceMock{listErr: errors.New("db down")}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/settings", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusInternalServerError {
		t.Fatalf("expected %d, got %d", fiber.StatusInternalServerError, resp.StatusCode)
	}
}

func TestUpdateSettingsRejectsInvalidJSON(t *testing.T) {
	svc := &settingServiceMock{}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString("{invalid-json"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if len(svc.setCalls) != 0 {
		t.Fatalf("did not expect Set calls on invalid json, got=%d", len(svc.setCalls))
	}
}

func TestUpdateSettingsRejectsEmptyBody(t *testing.T) {
	svc := &settingServiceMock{}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString("{}"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
}

func TestUpdateSettingsSanitizesAndSavesAllKeys(t *testing.T) {
	svc := &settingServiceMock{}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	payload := `{"cl_format":"<b>{INC}/CL/{YY}</b>","notes":"../allowed"}`
	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}

	if len(svc.setCalls) != 2 {
		t.Fatalf("expected 2 Set calls, got %d", len(svc.setCalls))
	}
	if svc.setCalls["cl_format"] != "{INC}/CL/{YY}" {
		t.Fatalf("expected sanitized cl_format, got %q", svc.setCalls["cl_format"])
	}
	if svc.setCalls["notes"] != "allowed" {
		t.Fatalf("expected path traversal removed from notes, got %q", svc.setCalls["notes"])
	}
}

func TestUpdateSettingsReturnsInternalErrorWhenSetFails(t *testing.T) {
	svc := &settingServiceMock{setErr: errors.New("write failed"), setErrKey: "timezone"}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	payload := `{"timezone":"Asia/Jakarta","currency":"IDR"}`
	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusInternalServerError {
		t.Fatalf("expected %d, got %d", fiber.StatusInternalServerError, resp.StatusCode)
	}
}

func TestUpdateSettingsRejectsInvalidBrandingColor(t *testing.T) {
	svc := &settingServiceMock{}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	payload := `{"branding_primary_color":"blue","branding_secondary_color":"#0F172A","document_header_title":"PT. ALTRAK 1978 - BANJARMASIN"}`
	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if len(svc.setCalls) != 0 {
		t.Fatalf("did not expect Set calls for invalid branding color, got=%d", len(svc.setCalls))
	}
}

func TestUpdateSettingsRejectsInvalidNumberingPayload(t *testing.T) {
	svc := &settingServiceMock{}
	h := NewHandler(svc)
	app := buildSettingHandlerTestApp(h)

	payload := `{"isf_format":"{YYYY}/{MM}/ISF-{INC}","isf_reset":"yearly","iom_format":"IOM/BJM/{MM}-{YYYY}/{INC}","iom_start":"0","cp_format":"{INC}/CS-BJM/{ROM}/{YYYY}","cp_reset":"global"}`
	req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if len(svc.setCalls) != 0 {
		t.Fatalf("did not expect Set calls for invalid numbering payload, got=%d", len(svc.setCalls))
	}
}
