package identity

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

	"github.com/google/uuid"
)

func TestTenantContextClientUsesApplicationMachineTokenAndETag(t *testing.T) {
	tenantID, applicationID := uuid.NewString(), uuid.NewString()
	var issuer string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/issuer/shared/.well-known/openid-configuration":
			_ = json.NewEncoder(w).Encode(map[string]any{
				"issuer": issuer, "token_endpoint": serverURL(r) + "/issuer/shared/token",
			})
		case "/issuer/shared/token":
			clientID, secret, ok := r.BasicAuth()
			if !ok || clientID != "gateway-machine" || secret != "machine-secret-material" ||
				r.FormValue("grant_type") != "client_credentials" || r.FormValue("scope") != "tenant.context.read" {
				t.Fatal("tenant context token request was not least-privilege client credentials")
			}
			_ = json.NewEncoder(w).Encode(map[string]any{
				"access_token": "opaque-machine-token", "token_type": "Bearer", "expires_in": 300,
			})
		case "/api/v1/runtime/tenants/" + tenantID:
			if r.Header.Get("Authorization") != "Bearer opaque-machine-token" {
				t.Fatal("tenant context request omitted the machine token")
			}
			if r.Header.Get("If-None-Match") == `"tenant-v7"` {
				w.WriteHeader(http.StatusNotModified)
				return
			}
			w.Header().Set("ETag", `"tenant-v7"`)
			_ = json.NewEncoder(w).Encode(TenantContext{
				ApplicationID: applicationID, TenantID: tenantID, DisplayName: "Tenant A", Slug: "tenant-a",
				TenantStatus: "active", TenantApplicationStatus: "active", Version: "7",
				UpdatedAt: time.Unix(1_780_000_000, 0).UTC(),
			})
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()
	issuer = server.URL + "/issuer/shared"
	client, err := NewTenantContextClient(
		"test", issuer, applicationID, server.URL+"/api/v1/runtime/tenants/{tenantId}",
		"urn:easyai:auth-center:tenant-context", "tenant.context.read",
		func(context.Context) (string, []byte, error) {
			return "gateway-machine", []byte("machine-secret-material"), nil
		}, server.Client(),
	)
	if err != nil {
		t.Fatal(err)
	}
	tenant, unchanged, err := client.Get(context.Background(), tenantID, "")
	if err != nil || unchanged || tenant.ETag != `"tenant-v7"` || !tenant.Active() {
		t.Fatalf("tenant=%#v unchanged=%t error=%v", tenant, unchanged, err)
	}
	_, unchanged, err = client.Get(context.Background(), tenantID, tenant.ETag)
	if err != nil || !unchanged {
		t.Fatalf("etag request unchanged=%t error=%v", unchanged, err)
	}
}

func TestTenantContextClientDistinguishesNotFoundFromTemporaryFailure(t *testing.T) {
	tenantID, applicationID := uuid.NewString(), uuid.NewString()
	for _, test := range []struct {
		status int
		want   error
	}{
		{status: http.StatusNotFound, want: ErrTenantContextNotFound},
		{status: http.StatusServiceUnavailable, want: ErrTenantContextUnavailable},
	} {
		t.Run(http.StatusText(test.status), func(t *testing.T) {
			var issuer string
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				switch {
				case r.URL.Path == "/issuer/shared/.well-known/openid-configuration":
					_ = json.NewEncoder(w).Encode(map[string]any{
						"issuer": issuer, "token_endpoint": serverURL(r) + "/issuer/shared/token",
					})
				case r.URL.Path == "/issuer/shared/token":
					_ = json.NewEncoder(w).Encode(map[string]any{
						"access_token": "opaque-machine-token", "token_type": "Bearer", "expires_in": 300,
					})
				default:
					w.WriteHeader(test.status)
				}
			}))
			defer server.Close()
			issuer = server.URL + "/issuer/shared"
			client, err := NewTenantContextClient(
				"test", issuer, applicationID, server.URL+"/api/v1/runtime/tenants/{tenantId}",
				"urn:easyai:auth-center:tenant-context", "tenant.context.read",
				func(context.Context) (string, []byte, error) {
					return "gateway-machine", []byte("machine-secret-material"), nil
				}, server.Client(),
			)
			if err != nil {
				t.Fatal(err)
			}
			if _, _, err := client.Get(context.Background(), tenantID, ""); err != test.want {
				t.Fatalf("error=%v want=%v", err, test.want)
			}
		})
	}
}

func serverURL(request *http.Request) string {
	return "http://" + request.Host
}
