package creditlimit

import (
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"system-altrak/internal/domain"
	"system-altrak/internal/dto"

	"github.com/gofiber/fiber/v2"
)

type creditLimitServiceMock struct {
	listData []domain.CreditLimit
	listErr  error

	getByIDData *domain.CreditLimit
	getByIDErr  error

	listCalls int
	getCalls  int

	lastListBranch uint
	lastListRole   string

	lastGetID     uint
	lastGetBranch uint
	lastGetRole   string
}

func (m *creditLimitServiceMock) Create(req *dto.CreditLimitRequest, userID, branchID uint) (*domain.CreditLimit, error) {
	return nil, nil
}

func (m *creditLimitServiceMock) Update(id uint, req *dto.CreditLimitRequest, branchID uint, role string) (*domain.CreditLimit, error) {
	return nil, nil
}

func (m *creditLimitServiceMock) GetByID(id uint, branchID uint, role string) (*domain.CreditLimit, error) {
	m.getCalls++
	m.lastGetID = id
	m.lastGetBranch = branchID
	m.lastGetRole = role
	if m.getByIDErr != nil {
		return nil, m.getByIDErr
	}
	if m.getByIDData != nil {
		return m.getByIDData, nil
	}
	return &domain.CreditLimit{}, nil
}

func (m *creditLimitServiceMock) List(branchID uint, role string) ([]domain.CreditLimit, error) {
	m.listCalls++
	m.lastListBranch = branchID
	m.lastListRole = role
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.listData, nil
}

func (m *creditLimitServiceMock) GeneratePDF(id uint, branchID uint, role string) ([]byte, string, error) {
	return nil, "", nil
}

func (m *creditLimitServiceMock) ExportExcel(branchID uint, role string) ([]byte, error) {
	return nil, nil
}

func (m *creditLimitServiceMock) Delete(id uint, branchID uint, role string) error {
	return nil
}

func buildCreditLimitHandlerTestApp(h *Handler, withLocals bool, branchID uint, role string) *fiber.App {
	app := fiber.New()
	if withLocals {
		app.Use(func(c *fiber.Ctx) error {
			c.Locals("current_branch_id", branchID)
			c.Locals("role", role)
			return c.Next()
		})
	}
	app.Get("/credit-limits", h.List)
	app.Get("/credit-limits/:id", h.GetOne)
	return app
}

func decodeCreditLimitResponseMap(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 TestListReturnsSuccessAndDelegatesContext(t *testing.T) {
	svc := &creditLimitServiceMock{
		listData: []domain.CreditLimit{{Customer: "PT Maju Jaya", CustomerCode: "C-01", BranchID: 7}},
	}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, true, 7, "admin")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits", 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)
	}
	if svc.listCalls != 1 || svc.lastListBranch != 7 || svc.lastListRole != "admin" {
		t.Fatalf("unexpected list args calls=%d branch=%d role=%q", svc.listCalls, svc.lastListBranch, svc.lastListRole)
	}

	body := decodeCreditLimitResponseMap(t, resp)
	if body["success"] != true {
		t.Fatalf("expected success=true, got=%v", body["success"])
	}
	rows, ok := body["data"].([]interface{})
	if !ok || len(rows) != 1 {
		t.Fatalf("expected one row in data, got=%T len=%d", body["data"], len(rows))
	}
}

func TestListReturnsInternalErrorWhenServiceFails(t *testing.T) {
	svc := &creditLimitServiceMock{listErr: errors.New("db unavailable")}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, true, 2, "user")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits", 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)
	}
	if svc.listCalls != 1 {
		t.Fatalf("expected one List call, got=%d", svc.listCalls)
	}
}

func TestListUsesZeroValuesWhenLocalsMissing(t *testing.T) {
	svc := &creditLimitServiceMock{listData: []domain.CreditLimit{}}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, false, 0, "")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits", 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)
	}
	if svc.lastListBranch != 0 || svc.lastListRole != "" {
		t.Fatalf("expected zero-value context forwarding, got branch=%d role=%q", svc.lastListBranch, svc.lastListRole)
	}
}

func TestGetOneRejectsInvalidID(t *testing.T) {
	svc := &creditLimitServiceMock{}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, true, 4, "manager")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits/not-a-number", nil)
	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.getCalls != 0 {
		t.Fatalf("did not expect GetByID call on invalid id, got=%d", svc.getCalls)
	}
}

func TestGetOneReturnsSuccessAndDelegatesContext(t *testing.T) {
	svc := &creditLimitServiceMock{
		getByIDData: &domain.CreditLimit{Customer: "PT Atlas", CustomerCode: "ATL-1", BranchID: 4},
	}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, true, 4, "manager")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits/9", 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)
	}
	if svc.getCalls != 1 || svc.lastGetID != 9 || svc.lastGetBranch != 4 || svc.lastGetRole != "manager" {
		t.Fatalf("unexpected GetByID args calls=%d id=%d branch=%d role=%q", svc.getCalls, svc.lastGetID, svc.lastGetBranch, svc.lastGetRole)
	}

	body := decodeCreditLimitResponseMap(t, resp)
	if body["success"] != true {
		t.Fatalf("expected success=true, got=%v", body["success"])
	}
}

func TestGetOneReturnsNotFoundWhenServiceFails(t *testing.T) {
	svc := &creditLimitServiceMock{getByIDErr: errors.New("not found")}
	h := NewHandler(svc)
	app := buildCreditLimitHandlerTestApp(h, true, 1, "staff")

	req := httptest.NewRequest(http.MethodGet, "/credit-limits/99", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if resp.StatusCode != fiber.StatusNotFound {
		t.Fatalf("expected %d, got %d", fiber.StatusNotFound, resp.StatusCode)
	}
	if svc.getCalls != 1 {
		t.Fatalf("expected one GetByID call, got=%d", svc.getCalls)
	}
}
