package galon

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/db"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
)

// GET /galon/status used to return a literal:
//
//	{"diToko": 45, "dipinjam": 12, "pengirmen": 3}
//
// rendered by the POS as stat tiles on its main screen. The database held three
// galon transactions in total, so a cashier was shown a store holding about
// sixty galons. This file pins the replacement: the two derivable counts come
// from the store, and the one that cannot be derived is null rather than a
// number.
//
// A wrong figure in a live count is worse than an absent one, because nothing
// on the screen distinguishes them. The POS template already renders a null as
// "—" via `?? '—'`, so null is a display the UI already knows how to show.
func TestStatusReturnsCountsFromStoreNotLiterals(t *testing.T) {
	f := newFakeStore()
	f.invRow = db.GalonInventoryStatusRow{OutstandingLoans: 4, InDelivery: 2}
	svc := NewService(f, &fakeSeq{}, time.UTC)
	h := &Handler{svc: svc}

	rec := httptest.NewRecorder()
	h.Status(rec, httptest.NewRequest(http.MethodGet, "/galon/status", nil))

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
	}
	var envelope struct {
		Data struct {
			DiToko     *int64 `json:"diToko"`
			Dipinjam   int64  `json:"dipinjam"`
			Pengiriman int64  `json:"pengiriman"`
		} `json:"data"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &envelope); err != nil {
		t.Fatal(err)
	}

	if envelope.Data.Dipinjam != 4 {
		t.Errorf("dipinjam = %d, want 4 (from the store's outstanding borrow_galon rows)", envelope.Data.Dipinjam)
	}
	if envelope.Data.Pengiriman != 2 {
		t.Errorf("pengiriman = %d, want 2 (from pending delivery-mode transactions)", envelope.Data.Pengiriman)
	}
	// The whole point: null, not 45, and not 0 either.
	if envelope.Data.DiToko != nil {
		t.Errorf("diToko = %v, want null — there is no galon unit registry to count, and 0 "+
			"would assert the store holds none", *envelope.Data.DiToko)
	}
	// And explicitly not the old literals.
	if envelope.Data.DiToko != nil || envelope.Data.Dipinjam == 12 || envelope.Data.Pengiriman == 3 {
		t.Error("response still matches the old hardcoded 45/12/3 fixture")
	}
}

// The keys are a contract with the POS tiles; renaming one silently blanks a
// stat tile because the template reads galonStatus?.diToko and friends.
func TestStatusKeepsThePOSKeyNames(t *testing.T) {
	f := newFakeStore()
	h := &Handler{svc: NewService(f, &fakeSeq{}, time.UTC)}
	rec := httptest.NewRecorder()
	h.Status(rec, httptest.NewRequest(http.MethodGet, "/galon/status", nil))

	var raw struct {
		Data map[string]any `json:"data"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil {
		t.Fatal(err)
	}
	for _, k := range []string{"diToko", "dipinjam", "pengiriman"} {
		if _, ok := raw.Data[k]; !ok {
			t.Errorf("key %q missing; the POS tile for it renders blank", k)
		}
	}
	if len(raw.Data) != 3 {
		t.Errorf("got %d keys, want 3: %v", len(raw.Data), raw.Data)
	}
}

// Tenant isolation: the tiles must be scoped to the caller's stores exactly like
// the transaction list behind them, or a POS user at one outlet sees another's
// galon position.
func TestStatusIsStoreScoped(t *testing.T) {
	f := newFakeStore()
	svc := NewService(f, &fakeSeq{}, time.UTC)

	// Platform-wide (admin/owner and anyone without a store binding) must be
	// unrestricted — nil, which the query reads as "no store filter".
	if _, err := svc.InventoryStatus(context.Background()); err != nil {
		t.Fatal(err)
	}
	if f.invAllowedStores != nil {
		t.Errorf("platform-wide caller got store filter %v, want nil", f.invAllowedStores)
	}

	// Restricted to one outlet: the query must receive exactly that outlet.
	scoped := auth.NewStoreScope([]int64{7})
	if _, err := svc.InventoryStatus(auth.WithStoreScope(context.Background(), scoped)); err != nil {
		t.Fatal(err)
	}
	if len(f.invAllowedStores) != 1 || f.invAllowedStores[0] != 7 {
		t.Errorf("store filter = %v, want [7]", f.invAllowedStores)
	}
}

var _ = middleware.RequestIDFrom
