package recon

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

// signatureServer answers 200 for the given paths and 404 for everything else
// (so the negative-control fingerprint guard is satisfied).
func signatureServer(paths ...string) *httptest.Server {
	set := map[string]struct{}{}
	for _, p := range paths {
		set[p] = struct{}{}
	}
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if _, ok := set[r.URL.Path]; ok {
			w.WriteHeader(http.StatusOK)
			return
		}
		w.WriteHeader(http.StatusNotFound)
	}))
}

func TestVapiProfile_FingerprintAndEndpoints(t *testing.T) {
	srv := signatureServer("/vapi/api5/users", "/vapi/api7/user/login")
	defer srv.Close()
	if !vapiProfile.matches(context.Background(), srv.URL, srv.Client()) {
		t.Fatal("vapi should fingerprint when its signature routes exist and garbage 404s")
	}
	eps := vapiProfile.endpoints(srv.URL)
	if len(eps) == 0 {
		t.Fatal("vapi endpoint pack is empty")
	}
	joined := ""
	bolaParam := false
	for _, e := range eps {
		joined += e.URL + " "
		if strings.Contains(e.URL, "/vapi/api1/user/") {
			for _, p := range e.Parameters {
				if p == "id" {
					bolaParam = true
				}
			}
		}
		if !strings.HasPrefix(e.URL, srv.URL+"/vapi/") {
			t.Errorf("endpoint not under the /vapi base: %q", e.URL)
		}
	}
	for _, want := range []string{"/vapi/api1/user/", "/vapi/api6/user", "/vapi/api8/user/login"} {
		if !strings.Contains(joined, want) {
			t.Errorf("vapi pack missing %q", want)
		}
	}
	if !bolaParam {
		t.Error("vapi api1 BOLA route should carry the id parameter")
	}
}

func TestPixiProfile_FingerprintAndEndpoints(t *testing.T) {
	srv := signatureServer("/api/pictures", "/api/admin/all_users")
	defer srv.Close()
	if !pixiProfile.matches(context.Background(), srv.URL, srv.Client()) {
		t.Fatal("pixi should fingerprint when its signature routes exist and garbage 404s")
	}
	eps := pixiProfile.endpoints(srv.URL)
	joined := ""
	userIDParam := false
	for _, e := range eps {
		joined += e.URL + " "
		if strings.Contains(e.URL, "/other_user_info") {
			for _, p := range e.Parameters {
				if p == "user_id" {
					userIDParam = true
				}
			}
		}
	}
	for _, want := range []string{"/api/user/register", "/api/user/login", "/api/admin/all_users", "/api/other_user_info"} {
		if !strings.Contains(joined, want) {
			t.Errorf("pixi pack missing %q", want)
		}
	}
	if !userIDParam {
		t.Error("pixi other_user_info BOLA route should carry the user_id parameter")
	}
}

func TestNewProfilesRegistered(t *testing.T) {
	have := map[string]bool{}
	for _, p := range apiProfiles {
		have[p.name] = true
	}
	for _, name := range []string{"vapi", "pixi"} {
		if !have[name] {
			t.Errorf("profile %q not registered in apiProfiles", name)
		}
	}
}

func TestNodegoatProfile_FingerprintAndEndpoints(t *testing.T) {
	srv := signatureServer("/login", "/signup")
	defer srv.Close()
	if !nodegoatProfile.matches(context.Background(), srv.URL, srv.Client()) {
		t.Fatal("nodegoat should fingerprint when /login + /signup exist and garbage 404s")
	}
	eps := nodegoatProfile.endpoints(srv.URL)
	joined := ""
	idor, openRedirect := false, false
	for _, e := range eps {
		joined += e.URL + " "
		if strings.Contains(e.URL, "/allocations/") {
			for _, p := range e.Parameters {
				if p == "userId" {
					idor = true
				}
			}
		}
		if strings.Contains(e.URL, "/learn") {
			for _, p := range e.Parameters {
				if p == "url" {
					openRedirect = true
				}
			}
		}
	}
	for _, want := range []string{"/allocations/", "/contributions", "/learn", "/profile"} {
		if !strings.Contains(joined, want) {
			t.Errorf("nodegoat pack missing %q", want)
		}
	}
	if !idor {
		t.Error("nodegoat /allocations should carry the userId param (IDOR)")
	}
	if !openRedirect {
		t.Error("nodegoat /learn should carry the url param (open redirect / SSRF)")
	}
}

func TestHackazonProfile_FingerprintAndEndpoints(t *testing.T) {
	srv := signatureServer("/api/product", "/api/category")
	defer srv.Close()
	if !hackazonProfile.matches(context.Background(), srv.URL, srv.Client()) {
		t.Fatal("hackazon should fingerprint when /api/product + /api/category exist and garbage 404s")
	}
	eps := hackazonProfile.endpoints(srv.URL)
	joined := ""
	cartBola := false
	for _, e := range eps {
		joined += e.URL + " "
		if strings.Contains(e.URL, "/cart/") {
			for _, p := range e.Parameters {
				if p == "cart_id" {
					cartBola = true
				}
			}
		}
	}
	for _, want := range []string{"/api/auth", "/api/cart/", "/api/order/", "/api/user/"} {
		if !strings.Contains(joined, want) {
			t.Errorf("hackazon pack missing %q", want)
		}
	}
	if !cartBola {
		t.Error("hackazon /api/cart should carry the cart_id param (BOLA)")
	}
}
