package integration_test

import (
	"context"
	"crypto/sha256"
	"strings"
	"testing"
	"time"

	"galaxy/integration/testenv"
	usermodel "galaxy/model/user"
	"galaxy/transcoder"

	"github.com/google/uuid"
)

// TestGatewayEdge_PublicBodyTooLarge tightens the public body size
// limit and asserts that the gateway rejects an oversize public auth
// payload before reaching backend.
func TestGatewayEdge_PublicBodyTooLarge(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{
		GatewayExtra: map[string]string{
			"GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_MAX_BODY_BYTES": "256",
		},
	})
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	huge := strings.Repeat("x", 4096)
	public := testenv.NewPublicRESTClient(plat.Gateway.HTTPURL)
	_, _, err := public.SendEmailCode(ctx, huge+"@example.com", "")
	if err == nil {
		t.Fatalf("expected error for oversize public payload, got nil")
	}
	if !strings.Contains(err.Error(), "413") && !strings.Contains(err.Error(), "request_too_large") {
		t.Fatalf("expected 413 or request_too_large, got: %v", err)
	}
}

// TestGatewayEdge_BadSignature corrupts the request signature and
// asserts the gateway rejects it as Unauthenticated.
func TestGatewayEdge_BadSignature(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{})
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	sess := testenv.RegisterSession(t, plat, "pilot+badsig@example.com")
	gw, err := sess.DialAuthenticated(ctx, plat)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer gw.Close()

	payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{})
	if err != nil {
		t.Fatalf("encode payload: %v", err)
	}
	bogus := make([]byte, 64)
	_, err = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{
		OverrideSignature: bogus,
	})
	if err == nil {
		t.Fatalf("expected Unauthenticated for bad signature")
	}
	if !testenv.IsUnauthenticated(err) {
		t.Fatalf("expected Unauthenticated, got: %v", err)
	}
}

// TestGatewayEdge_PayloadHashMismatch sends a request whose
// payload_hash is not the SHA-256 of payload_bytes and asserts the
// gateway rejects it.
func TestGatewayEdge_PayloadHashMismatch(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{})
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	sess := testenv.RegisterSession(t, plat, "pilot+hash@example.com")
	gw, err := sess.DialAuthenticated(ctx, plat)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer gw.Close()

	payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{})
	if err != nil {
		t.Fatalf("encode payload: %v", err)
	}
	// The signed canonical bytes still use this wrong hash; gateway
	// recomputes and should detect the mismatch independently of the
	// signature check.
	wrong := sha256.Sum256([]byte("not-the-payload"))
	_, err = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{
		OverridePayloadHash: wrong[:],
	})
	if err == nil {
		t.Fatalf("expected rejection for payload_hash mismatch")
	}
	if !testenv.IsUnauthenticated(err) && !testenv.IsInvalidArgument(err) {
		t.Fatalf("expected Unauthenticated or InvalidArgument, got: %v", err)
	}
}

// TestGatewayEdge_StaleTimestamp tightens freshness window to 1
// second, then submits a request whose timestamp is 30 seconds in
// the past, and asserts the gateway rejects it as stale.
func TestGatewayEdge_StaleTimestamp(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{
		GatewayExtra: map[string]string{
			"GATEWAY_AUTHENTICATED_GRPC_FRESHNESS_WINDOW": "1s",
		},
	})
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	sess := testenv.RegisterSession(t, plat, "pilot+stale@example.com")
	gw, err := sess.DialAuthenticated(ctx, plat)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer gw.Close()

	payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{})
	if err != nil {
		t.Fatalf("encode payload: %v", err)
	}
	stale := time.Now().Add(-30 * time.Second).UnixMilli()
	_, err = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{
		TimestampMS: stale,
	})
	if err == nil {
		t.Fatalf("expected rejection for stale timestamp")
	}
	if !testenv.IsUnauthenticated(err) && !testenv.IsInvalidArgument(err) && !testenv.IsFailedPrecondition(err) {
		t.Fatalf("expected Unauthenticated, InvalidArgument or FailedPrecondition, got: %v", err)
	}
}

// TestGatewayEdge_UnknownSession addresses a session id that backend
// has never seen; gateway must reject before forwarding.
func TestGatewayEdge_UnknownSession(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{})
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	sess := testenv.RegisterSession(t, plat, "pilot+unknown@example.com")
	gw, err := sess.DialAuthenticated(ctx, plat)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer gw.Close()

	payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{})
	if err != nil {
		t.Fatalf("encode payload: %v", err)
	}
	_, err = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{
		OverrideSessionID: uuid.NewString(),
	})
	if err == nil {
		t.Fatalf("expected rejection for unknown session")
	}
	if !testenv.IsUnauthenticated(err) {
		t.Fatalf("expected Unauthenticated, got: %v", err)
	}
}

// TestGatewayEdge_UnsupportedProtocolVersion sets protocol_version
// to an unknown literal and asserts gateway rejection.
func TestGatewayEdge_UnsupportedProtocolVersion(t *testing.T) {
	plat := testenv.Bootstrap(t, testenv.BootstrapOptions{})
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	sess := testenv.RegisterSession(t, plat, "pilot+protover@example.com")
	gw, err := sess.DialAuthenticated(ctx, plat)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer gw.Close()

	payload, err := transcoder.GetMyAccountRequestToPayload(&usermodel.GetMyAccountRequest{})
	if err != nil {
		t.Fatalf("encode payload: %v", err)
	}
	_, err = gw.Execute(ctx, usermodel.MessageTypeGetMyAccount, payload, testenv.ExecuteOptions{
		OverrideProtocolVersion: "v999",
	})
	if err == nil {
		t.Fatalf("expected rejection for unsupported protocol_version")
	}
	if !testenv.IsInvalidArgument(err) && !testenv.IsUnauthenticated(err) && !testenv.IsFailedPrecondition(err) {
		t.Fatalf("expected InvalidArgument, Unauthenticated or FailedPrecondition, got: %v", err)
	}
}
