package middleware

import (
	"net/http/httptest"
	"strings"
	"testing"

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

// The outlet a request names has to be found wherever the route happens to put
// it. These cover the extraction alone — it is the part that decides whether
// the authorisation check runs at all, and a miss here reads exactly like a
// pass.

func locationFor(t *testing.T, method, target, body string) int {
	t.Helper()

	app := fiber.New()
	found := -1
	app.All("/probe", func(c *fiber.Ctx) error {
		found = requestedLocation(c)
		return c.SendStatus(fiber.StatusOK)
	})

	var reader *strings.Reader
	if body == "" {
		reader = strings.NewReader("")
	} else {
		reader = strings.NewReader(body)
	}

	req := httptest.NewRequest(method, target, reader)
	if body != "" {
		req.Header.Set("Content-Type", "application/json")
	}

	if _, err := app.Test(req); err != nil {
		t.Fatalf("probing: %v", err)
	}
	return found
}

func TestTheOutletIsFoundUnderEveryNameTheRoutesUse(t *testing.T) {
	// Three spellings for one thing across the POS routes. Missing any of them
	// leaves that route unguarded.
	cases := []struct {
		name   string
		target string
	}{
		{"catalogue says store_id", "/probe?store_id=1135"},
		{"sales say locationid", "/probe?locationid=1135"},
		{"health says location_id", "/probe?location_id=1135"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := locationFor(t, "GET", tc.target, ""); got != 1135 {
				t.Fatalf("wanted outlet 1135, got %d", got)
			}
		})
	}
}

// The two routes that *write* carry the outlet in a JSON batch and never in the
// URL. Checking only the query string would leave the exact call that posts
// bills into another tenant's books unguarded.
func TestTheOutletIsFoundInAnIngestBody(t *testing.T) {
	body := `{"batch_id":"b1","terminal_id":"T5EDD","store_id":"1135","orders":[]}`

	if got := locationFor(t, "POST", "/probe", body); got != 1135 {
		t.Fatalf("wanted outlet 1135 from the batch body, got %d", got)
	}
}

// The till quotes its ids; other callers send them bare. Accepting only one
// shape silently skips the check for the other.
func TestAnOutletIsReadWhetherQuotedOrNot(t *testing.T) {
	quoted := `{"store_id":"1135"}`
	bare := `{"store_id":1135}`

	if got := locationFor(t, "POST", "/probe", quoted); got != 1135 {
		t.Fatalf("quoted store_id: wanted 1135, got %d", got)
	}
	if got := locationFor(t, "POST", "/probe", bare); got != 1135 {
		t.Fatalf("bare store_id: wanted 1135, got %d", got)
	}
}

func TestAHealthBodyNamesItsOutlet(t *testing.T) {
	body := `{"terminal_id":"T5EDD","location_id":"1135","status":"online"}`

	if got := locationFor(t, "POST", "/probe", body); got != 1135 {
		t.Fatalf("wanted outlet 1135 from the health body, got %d", got)
	}
}

// A request naming no outlet is not an error — /session names none — so it must
// come back as "nothing to check" rather than as outlet zero.
func TestARequestNamingNoOutletReportsNone(t *testing.T) {
	if got := locationFor(t, "GET", "/probe", ""); got != 0 {
		t.Fatalf("wanted 0 for a request naming no outlet, got %d", got)
	}
	if got := locationFor(t, "POST", "/probe", `{"batch_id":"b1"}`); got != 0 {
		t.Fatalf("wanted 0 for a body naming no outlet, got %d", got)
	}
}

// A body this middleware cannot parse must not be treated as naming an outlet.
// The handler will refuse it on its own terms; guessing here would either
// reject a good request or wave a bad one through.
func TestAnUnparseableBodyNamesNoOutlet(t *testing.T) {
	if got := locationFor(t, "POST", "/probe", `{not json at all`); got != 0 {
		t.Fatalf("wanted 0 for an unparseable body, got %d", got)
	}
}

func TestABearerTokenIsReadInEveryFormTheFieldSends(t *testing.T) {
	app := fiber.New()
	var got string
	app.Get("/probe", func(c *fiber.Ctx) error {
		got = bearerToken(c)
		return c.SendStatus(fiber.StatusOK)
	})

	cases := []struct {
		name   string
		header string
		value  string
		want   string
	}{
		{"the standard form", "Authorization", "Bearer abc.def", "abc.def"},
		{"a bare token, which terminals send", "Authorization", "abc.def", "abc.def"},
		{"the fallback header", "X-Pos-Token", "abc.def", "abc.def"},
		{"a scheme we do not issue", "Authorization", "Basic dXNlcjpwdw==", ""},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got = ""
			req := httptest.NewRequest("GET", "/probe", nil)
			req.Header.Set(tc.header, tc.value)
			if _, err := app.Test(req); err != nil {
				t.Fatalf("probing: %v", err)
			}
			if got != tc.want {
				t.Fatalf("wanted %q, got %q", tc.want, got)
			}
		})
	}
}
