package runner

import (
	"net/http"
	"testing"
)

func TestAuthCredentialSpecContract(t *testing.T) {
	t.Parallel()

	webhookToken := CredentialField{
		Name: "apiKey", DisplayName: "token", Required: true, Secret: true,
	}
	signingSecret := CredentialField{
		Name: "signingSecret", DisplayName: "secret", Secret: true,
	}

	t.Run("accepts connection-scoped webhook credentials", func(t *testing.T) {
		t.Parallel()
		spec := AuthSpec{Kind: AuthCredential, Fields: []CredentialField{webhookToken, signingSecret}}
		if err := spec.ValidateSpec(); err != nil {
			t.Fatalf("valid webhook contract rejected: %v", err)
		}
		if err := spec.Validate(Credentials{"apiKey": "token", "signingSecret": "secret"}); err != nil {
			t.Fatalf("valid credentials rejected: %v", err)
		}
		if err := spec.Validate(Credentials{"apiKey": "token"}); err != nil {
			t.Fatalf("optional secret must be omittable: %v", err)
		}
	})

	t.Run("requires at least one required and secret field", func(t *testing.T) {
		t.Parallel()
		spec := AuthSpec{Kind: AuthCredential, Fields: []CredentialField{signingSecret}}
		if err := spec.ValidateSpec(); err == nil {
			t.Fatal("optional-only fields must be rejected")
		}
	})

	t.Run("rejects injection settings and header bindings", func(t *testing.T) {
		t.Parallel()
		withHeader := webhookToken
		withHeader.HeaderName = "X-Token"
		cases := []AuthSpec{
			{Kind: AuthCredential, Header: "X-Token", Fields: []CredentialField{webhookToken}},
			{Kind: AuthCredential, QueryName: "token", Fields: []CredentialField{webhookToken}},
			{Kind: AuthCredential, BasicUsername: "u", Fields: []CredentialField{webhookToken}},
			{Kind: AuthCredential, BasicUsernameField: "u", BasicPasswordField: "p",
				Fields: []CredentialField{webhookToken}},
			{Kind: AuthCredential, Fields: []CredentialField{withHeader}},
		}
		for _, spec := range cases {
			if err := spec.ValidateSpec(); err == nil {
				t.Fatalf("injection setting under the credentials kind must be rejected: %+v", spec)
			}
		}
	})

	t.Run("injects nothing onto requests", func(t *testing.T) {
		t.Parallel()
		spec := AuthSpec{Kind: AuthCredential, Fields: []CredentialField{webhookToken, signingSecret}}
		request := newRequestForAuthTest()
		if err := spec.applyAuthentication(request, Credentials{"apiKey": "token"}); err != nil {
			t.Fatalf("credentials auth must not fail binding: %v", err)
		}
		if got := request.Header.Get("Authorization"); got != "" {
			t.Fatalf("credentials auth must not inject Authorization, got %q", got)
		}
	})
}

func TestAuthQuerySpecContract(t *testing.T) {
	t.Parallel()

	apiKey := APIKeyField("API key", "Provider API key")
	spec := AuthSpec{
		Kind:      AuthQuery,
		QueryName: "apikey",
		Fields:    []CredentialField{apiKey},
	}
	if err := spec.ValidateSpec(); err != nil {
		t.Fatalf("valid query auth rejected: %v", err)
	}
	request := newRequestForAuthTest()
	request.URL.RawQuery = "symbol=AAPL&apikey=untrusted"
	if err := spec.applyAuthentication(request, Credentials{"apiKey": "secret"}); err != nil {
		t.Fatalf("query auth binding failed: %v", err)
	}
	if got := request.URL.Query().Get("apikey"); got != "secret" {
		t.Fatalf("query auth must overwrite Agent input, got %q", got)
	}
	if got := request.URL.Query().Get("symbol"); got != "AAPL" {
		t.Fatalf("query auth must preserve provider query, got %q", got)
	}

	for _, invalid := range []AuthSpec{
		{Kind: AuthQuery, Fields: []CredentialField{apiKey}},
		{Kind: AuthQuery, QueryName: "api key", Fields: []CredentialField{apiKey}},
		{Kind: AuthQuery, QueryName: "apikey&other", Fields: []CredentialField{apiKey}},
		{Kind: AuthQuery, QueryName: "apikey", Header: "X-Key", Fields: []CredentialField{apiKey}},
		{Kind: AuthQuery, QueryName: "apikey", BasicUsername: "user", Fields: []CredentialField{apiKey}},
	} {
		if err := invalid.ValidateSpec(); err == nil {
			t.Fatalf("invalid query auth must fail closed: %+v", invalid)
		}
	}
}

func TestQueryNameRejectedOutsideQueryAuth(t *testing.T) {
	t.Parallel()

	apiKey := APIKeyField("API key", "Provider API key")
	for _, kind := range []AuthKind{
		AuthNone, AuthBearer, AuthHeader, AuthBasic, AuthHeaders, AuthCredential,
	} {
		spec := AuthSpec{Kind: kind, QueryName: "apikey", Fields: []CredentialField{apiKey}}
		switch kind {
		case AuthNone:
			spec.Fields = nil
		case AuthHeader:
			spec.Header = "X-API-Key"
		case AuthBasic:
			spec.BasicUsername = "user"
		}
		if err := spec.ValidateSpec(); err == nil {
			t.Fatalf("%s auth must reject a query parameter target", kind)
		}
	}
}

func newRequestForAuthTest() *http.Request {
	request, err := http.NewRequest(http.MethodPost, "https://provider.example.com/hook", nil)
	if err != nil {
		panic(err)
	}
	return request
}
