package packs

import (
	"strings"
	"testing"
)

func TestLoadConnectorCalloutProfileOutboundCapabilities(t *testing.T) {
	raw := []byte(`{
  "profileName":"deal-company-write",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.write",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
	"outboundCapabilities":[
		{"operation":"create","targetObject":"deals","method":"POST","pathTemplate":"/crm/v3/objects/deals"},
		{"operation":"update","targetObject":"deals","method":"PATCH","pathTemplate":"/crm/v3/objects/deals/{remoteId}"},
		{"operation":"search","targetObject":"companies","method":"POST","pathTemplate":"/crm/v3/objects/companies/search"},
		{"operation":"associate","targetObject":"deals","associationTargetObject":"companies","method":"PUT","pathTemplate":"/crm/v3/objects/deals/{fromId}/associations/companies/{toId}/5"}
	]
}`)
	got, err := LoadConnectorCalloutProfileOutboundCapabilities(
		raw,
		"connector-a",
		"connector-a/deal-company-write",
	)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 4 || got[2].Operation != "search" || got[2].Method != "POST" ||
		got[3].Operation != "associate" || got[3].AssociationTargetObject != "companies" || got[3].Method != "PUT" {
		t.Fatalf("outbound capabilities = %#v", got)
	}
	got[0].TargetObject = "mutated"
	again, err := LoadConnectorCalloutProfileOutboundCapabilities(
		raw,
		"connector-a",
		"connector-a/deal-company-write",
	)
	if err != nil {
		t.Fatal(err)
	}
	if again[0].TargetObject != "deals" {
		t.Fatalf("loader retained caller mutation: %#v", again)
	}
}

func TestLoadConnectorCalloutProfileOutboundCapabilitiesRejectsInvalidSearchAuthority(t *testing.T) {
	valid := []byte(`{
  "profileName":"company-search",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.read",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
  "outboundCapabilities":[
    {"operation":"search","targetObject":"companies","method":"POST","pathTemplate":"/crm/v3/objects/companies/search"}
  ]
}`)
	tests := []struct {
		name string
		raw  string
		want string
	}{
		{name: "method", raw: strings.Replace(string(valid), `"method":"POST"`, `"method":"GET"`, 1), want: "search method must be POST"},
		{name: "association target", raw: strings.Replace(string(valid), `"targetObject":"companies"`, `"targetObject":"companies","associationTargetObject":"deals"`, 1), want: "search associationTargetObject must be blank"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := LoadConnectorCalloutProfileOutboundCapabilities(
				[]byte(test.raw),
				"connector-a",
				"connector-a/company-search",
			)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want %q", err, test.want)
			}
		})
	}
}

func TestLoadConnectorCalloutProfileOutboundCapabilitiesAcceptsHistoricalBatchCompatibility(t *testing.T) {
	raw := []byte(`{
  "profileName":"legacy-batch",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.read",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
  "batchSpec":{
    "method":"POST",
    "relativePath":"/crm/v3/objects/batch/read",
    "maxBatchSize":100,
    "idsRequestField":"inputs",
    "credentialRef":"hubspot-main",
    "endpointClass":"crm.read"
  }
}`)
	got, err := LoadConnectorCalloutProfileOutboundCapabilities(
		raw,
		"connector-a",
		"connector-a/legacy-batch",
	)
	if err != nil {
		t.Fatalf("historical batch compatibility rejected: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("outbound capabilities = %#v, want none", got)
	}
}

func TestLoadConnectorCalloutProfileOutboundCapabilitiesAcceptsInactiveProbeQueryParams(t *testing.T) {
	raw := []byte(`{
  "profileName":"contacts",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.read",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
  "batchSpec":{
    "method":"POST",
    "relativePath":"/crm/v3/objects/contacts/batch/read",
    "maxBatchSize":100,
    "idsRequestField":"inputs",
    "idResponseField":"id",
    "credentialRef":"hubspot-main",
    "endpointClass":"crm.read",
    "inactiveProbeQueryParams":{"archived":["true"]}
  }
}`)
	got, err := LoadConnectorCalloutProfileOutboundCapabilities(
		raw,
		"connector-a",
		"connector-a/contacts",
	)
	if err != nil {
		t.Fatalf("current inactive-probe profile rejected: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("outbound capabilities = %#v, want none", got)
	}
}

func TestLoadConnectorCalloutProfileOutboundCapabilitiesAllowsMultipleExactPaths(t *testing.T) {
	raw := []byte(`{
  "profileName":"multi-path",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.write",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
  "outboundCapabilities":[
    {"operation":"update","targetObject":"deals","method":"PATCH","pathTemplate":"/crm/v3/objects/deals/{remoteId}"},
    {"operation":"update","targetObject":"deals","method":"PATCH","pathTemplate":"/crm/v4/objects/deals/{remoteId}"}
  ]
}`)
	got, err := LoadConnectorCalloutProfileOutboundCapabilities(
		raw,
		"connector-a",
		"connector-a/multi-path",
	)
	if err != nil {
		t.Fatalf("multiple exact paths rejected: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("outbound capabilities = %#v, want two", got)
	}
}

func TestLoadConnectorCalloutProfileOutboundCapabilitiesRejectsInvalidAuthority(t *testing.T) {
	valid := `{
  "profileName":"deal-company-write",
  "credentialRef":"hubspot-main",
  "endpointClass":"crm.write",
  "retryPolicy":{"maxAttempts":1,"baseDelay":0,"multiplier":0,"maxDelay":0},
  "outboundCapabilities":[
    {"operation":"associate","targetObject":"deals","associationTargetObject":"companies","method":"PUT","pathTemplate":"/crm/v3/objects/deals/{fromId}/associations/companies/{toId}/5"}
  ]
}`
	tests := []struct {
		name string
		raw  string
		want string
	}{
		{
			name: "binding identity",
			raw:  strings.Replace(valid, `"profileName":"deal-company-write"`, `"profileName":"other"`, 1),
			want: "disagrees with its binding",
		},
		{
			name: "missing association target",
			raw:  strings.Replace(valid, `,"associationTargetObject":"companies"`, "", 1),
			want: "associationTargetObject is blank",
		},
		{
			name: "noncanonical method",
			raw:  strings.Replace(valid, `"method":"PUT"`, `"method":"put"`, 1),
			want: "must be uppercase",
		},
		{
			name: "unknown field",
			raw:  strings.Replace(valid, `"profileName":"deal-company-write"`, `"profileName":"deal-company-write","wildcard":true`, 1),
			want: "unknown field",
		},
		{
			name: "duplicate exact capability",
			raw: strings.Replace(
				valid,
				"\n  ]\n}",
				`,
    {"operation":"associate","targetObject":"deals","associationTargetObject":"companies","method":"PUT","pathTemplate":"/crm/v3/objects/deals/{fromId}/associations/companies/{toId}/5"}
  ]
}`,
				1,
			),
			want: "repeats associate capability",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := LoadConnectorCalloutProfileOutboundCapabilities(
				[]byte(test.raw),
				"connector-a",
				"connector-a/deal-company-write",
			)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want %q", err, test.want)
			}
		})
	}
}
