package user

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"system-altrak/internal/domain"

	"github.com/gofiber/fiber/v2"
)

type userServiceMock struct {
	listUsers []domain.User
	listErr   error
	createErr error
	updateErr error
	deleteErr error

	createCalls int
	updateCalls int
	deleteCalls int

	createUser domain.User
	updateUser domain.User

	createIP string
	createUA string
	updateIP string
	updateUA string

	deleteID      uint
	deleteActorID uint
	deleteIP      string
	deleteUA      string
}

func (m *userServiceMock) List() ([]domain.User, error) {
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.listUsers, nil
}

func (m *userServiceMock) Create(user *domain.User, ip, userAgent string) error {
	m.createCalls++
	m.createUser = *user
	m.createIP = ip
	m.createUA = userAgent
	return m.createErr
}

func (m *userServiceMock) Update(user *domain.User, ip, userAgent string) error {
	m.updateCalls++
	m.updateUser = *user
	m.updateIP = ip
	m.updateUA = userAgent
	return m.updateErr
}

func (m *userServiceMock) Delete(id, actorID uint, ip, userAgent string) error {
	m.deleteCalls++
	m.deleteID = id
	m.deleteActorID = actorID
	m.deleteIP = ip
	m.deleteUA = userAgent
	return m.deleteErr
}

func buildUserHandlerTestApp(h *Handler, actorID uint) *fiber.App {
	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		c.Locals("user_id", actorID)
		return c.Next()
	})
	app.Get("/users", h.List)
	app.Post("/users", h.Create)
	app.Put("/users/:id", h.Update)
	app.Delete("/users/:id", h.Delete)
	return app
}

func decodeBodyAsMap(t *testing.T, resp *http.Response) map[string]interface{} {
	t.Helper()
	var body map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	return body
}

func TestListReturnsUsers(t *testing.T) {
	svc := &userServiceMock{listUsers: []domain.User{{FullName: "Alice", Username: "alice"}}}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	req := httptest.NewRequest(http.MethodGet, "/users", 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)
	}

	body := decodeBodyAsMap(t, resp)
	if body["success"] != true {
		t.Fatalf("expected success=true, got=%v", body["success"])
	}

	users, ok := body["data"].([]interface{})
	if !ok || len(users) != 1 {
		t.Fatalf("expected one user in response data, got=%T len=%d", body["data"], len(users))
	}
}

func TestListReturnsInternalErrorWhenServiceFails(t *testing.T) {
	svc := &userServiceMock{listErr: errors.New("db failure")}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	req := httptest.NewRequest(http.MethodGet, "/users", 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 TestCreateRejectsInvalidJSON(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	req := httptest.NewRequest(http.MethodPost, "/users", bytes.NewBufferString("{bad-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 svc.createCalls != 0 {
		t.Fatalf("did not expect Create call, got=%d", svc.createCalls)
	}
}

func TestCreateRejectsInvalidPayload(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	payload := `{"full_name":"Alice","username":"abc","password":"123","role":"admin"}`
	req := httptest.NewRequest(http.MethodPost, "/users", 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 svc.createCalls != 0 {
		t.Fatalf("did not expect Create call on invalid payload, got=%d", svc.createCalls)
	}
}

func TestCreateSanitizesAndDelegates(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	payload := `{"full_name":"<b>Alice Doe</b>","username":"<b>alice01</b>","password":"secret123","role":"admin","branch_id":2}`
	req := httptest.NewRequest(http.MethodPost, "/users", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "unit-test-agent")
	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 svc.createCalls != 1 {
		t.Fatalf("expected one Create call, got=%d", svc.createCalls)
	}
	if svc.createUser.FullName != "Alice Doe" {
		t.Fatalf("expected sanitized full name, got=%q", svc.createUser.FullName)
	}
	if svc.createUser.Username != "alice01" {
		t.Fatalf("expected sanitized username, got=%q", svc.createUser.Username)
	}
	if svc.createUA != "unit-test-agent" {
		t.Fatalf("expected forwarded user-agent, got=%q", svc.createUA)
	}

	body := decodeBodyAsMap(t, resp)
	data, ok := body["data"].(map[string]interface{})
	if !ok {
		t.Fatalf("expected object data response, got=%T", body["data"])
	}
	if _, exists := data["password_hash"]; exists {
		t.Fatal("password_hash must not be exposed in create response")
	}
}

func TestUpdateRejectsInvalidID(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	req := httptest.NewRequest(http.MethodPut, "/users/not-a-number", bytes.NewBufferString(`{"full_name":"A","username":"alice01","role":"admin"}`))
	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 svc.updateCalls != 0 {
		t.Fatalf("did not expect Update call, got=%d", svc.updateCalls)
	}
}

func TestUpdateSanitizesAndDelegates(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 1)

	payload := `{"full_name":"<i>Bob User</i>","username":"<b>bobuser</b>","password":"secret123","role":"manager","branch_id":3}`
	req := httptest.NewRequest(http.MethodPut, "/users/7", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "unit-test-agent")
	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 svc.updateCalls != 1 {
		t.Fatalf("expected one Update call, got=%d", svc.updateCalls)
	}
	if svc.updateUser.ID != 7 {
		t.Fatalf("expected update user id=7, got=%d", svc.updateUser.ID)
	}
	if svc.updateUser.FullName != "Bob User" || svc.updateUser.Username != "bobuser" {
		t.Fatalf("expected sanitized update payload, got full_name=%q username=%q", svc.updateUser.FullName, svc.updateUser.Username)
	}
	if svc.updateUA != "unit-test-agent" {
		t.Fatalf("expected forwarded user-agent, got=%q", svc.updateUA)
	}
}

func TestDeleteRejectsSelfDelete(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 5)

	req := httptest.NewRequest(http.MethodDelete, "/users/5", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusForbidden {
		t.Fatalf("expected %d, got %d", fiber.StatusForbidden, resp.StatusCode)
	}
	if svc.deleteCalls != 0 {
		t.Fatalf("did not expect Delete call for self-delete, got=%d", svc.deleteCalls)
	}
}

func TestDeleteDelegatesWithActorContext(t *testing.T) {
	svc := &userServiceMock{}
	h := NewHandler(svc)
	app := buildUserHandlerTestApp(h, 99)

	req := httptest.NewRequest(http.MethodDelete, "/users/6", nil)
	req.Header.Set("User-Agent", "unit-test-agent")
	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 svc.deleteCalls != 1 {
		t.Fatalf("expected one Delete call, got=%d", svc.deleteCalls)
	}
	if svc.deleteID != 6 || svc.deleteActorID != 99 {
		t.Fatalf("unexpected delete args: id=%d actor=%d", svc.deleteID, svc.deleteActorID)
	}
	if svc.deleteUA != "unit-test-agent" {
		t.Fatalf("expected forwarded user-agent, got=%q", svc.deleteUA)
	}
}
