package integrationservice

import (
	"context"
	"errors"
	"sort"
	"strings"
	"testing"

	connectorcapabilities "github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
	connectorrouting "github.com/pingcap-inc/nexuscrm-connectors/features/connector/inboundrouting"
	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	flowcommandv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv1"
	flowcommandv2 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv2"
)

type integrationRouteResolverProbe struct {
	calls      int
	version    int64
	definition string
}

func (p *integrationRouteResolverProbe) ResolveFlow(context.Context, string, string, string) (int64, string, error) {
	p.calls++
	return p.version, p.definition, nil
}

func (p *integrationRouteResolverProbe) ResolveFlowByVersion(context.Context, string, string, int64) (string, error) {
	p.calls++
	return p.definition, nil
}

func (p *integrationRouteResolverProbe) ResolveExecutionSpecByVersion(context.Context, string, string, int64) (connectorrouting.RouteExecutionSpec, error) {
	p.calls++
	return connectorrouting.RouteExecutionSpec{FlowDeveloperName: p.definition}, nil
}

func TestIntegrationServingAuthoritySelectsOnlyRuntimeCommandRoutePacks(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound-z", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound-a", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorCalloutProfile, "connector-a", "install-a", "connector-a/default", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorFieldMapping, "connector-b", "install-b", "connector-b/mapping", "b"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	for index := range view.Bindings {
		if view.Bindings[index].Kind == catalogv1.BindingConnectorCalloutProfile {
			view.Bindings[index].CredentialRefs = []string{"credential-a"}
		}
	}
	sortIntegrationServingBindings(view.Bindings)
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)

	authority, err := newIntegrationServingAuthority(view)
	if err != nil {
		t.Fatal(err)
	}
	base := &integrationRouteResolverProbe{version: 1, definition: "BoundedInbound"}
	selection, err := authority.RuntimeServingSelection(base)
	if err != nil {
		t.Fatal(err)
	}
	if len(selection.Packs) != 1 || selection.Packs[0].Namespace != "connector-a" ||
		selection.Packs[0].InstalledPackID != "install-a" ||
		len(selection.Packs[0].InboundRoutes) != 2 ||
		selection.Packs[0].InboundRoutes[0].EventKind != "inbound-a" || selection.Packs[0].InboundRoutes[0].RouteVersion != 1 ||
		selection.Packs[0].InboundRoutes[1].EventKind != "inbound-z" || selection.Packs[0].InboundRoutes[1].RouteVersion != 1 ||
		len(selection.Packs[0].CalloutProfiles) != 1 || selection.Packs[0].CalloutProfiles[0].Name != "default" ||
		len(selection.Packs[0].CalloutProfiles[0].CredentialRefs) != 1 || selection.Packs[0].CalloutProfiles[0].CredentialRefs[0] != "credential-a" {
		t.Fatalf("Runtime serving selection = %#v", selection.Packs)
	}
	version, definition, err := selection.RouteResolver.ResolveFlow(t.Context(), "connector-a", "install-a", "inbound-a")
	if err != nil || version != 1 || definition != "BoundedInbound" || base.calls != 1 {
		t.Fatalf("authorized route = (%d, %q, %v), calls=%d", version, definition, err, base.calls)
	}
	if _, _, err := selection.RouteResolver.ResolveFlow(t.Context(), "connector-b", "install-b", "mapping"); !errors.Is(err, connectorrouting.ErrRouteNotDeclared) || base.calls != 1 {
		t.Fatalf("unauthorized route error = %v, base calls=%d", err, base.calls)
	}
	gateCalls := 0
	gate := authority.CapabilityGate(func(context.Context, string, connectorcapabilities.CapabilityKind, string) (bool, error) {
		gateCalls++
		return true, nil
	})
	if allowed, err := gate(t.Context(), "connector-a", connectorcapabilities.EnvelopeParser, "install-a"); err != nil || !allowed || gateCalls != 1 {
		t.Fatalf("authorized capability = (%v, %v), calls=%d", allowed, err, gateCalls)
	}
	if allowed, err := gate(t.Context(), "connector-b", connectorcapabilities.EnvelopeParser, "install-b"); err != nil || allowed || gateCalls != 1 {
		t.Fatalf("non-serving capability = (%v, %v), calls=%d", allowed, err, gateCalls)
	}
}

func TestIntegrationServingAuthorityFailsClosedOnProjectionDrift(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	authority, err := newIntegrationServingAuthority(view)
	if err != nil {
		t.Fatal(err)
	}
	selection, err := authority.RuntimeServingSelection(&integrationRouteResolverProbe{version: 1, definition: "DifferentFlow"})
	if err != nil {
		t.Fatal(err)
	}
	if _, _, err := selection.RouteResolver.ResolveFlow(t.Context(), "connector-a", "install-a", "inbound"); err == nil || !strings.Contains(err.Error(), "drifted") {
		t.Fatalf("route drift error = %v", err)
	}
}

func TestIntegrationServingAuthorityRetainsExactSupportedRuntimeCommand(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	want := *view.Bindings[0].RuntimeCommand
	want.DefinitionVersion = "17"
	view.Bindings[0].RuntimeCommand = &want
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)

	authority, err := newIntegrationServingAuthority(view)
	if err != nil {
		t.Fatal(err)
	}
	got := authority.routes[integrationRouteKey("connector-a", "install-a", "inbound")].runtimeCommand
	if got != want {
		t.Fatalf("Runtime command binding = %#v, want %#v", got, want)
	}
}

func TestIntegrationServingAuthorityRetainsDetachedCompleteBindingIdentities(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(
			catalogv1.BindingConnectorCalloutProfile,
			"connector-a",
			"install-a",
			"connector-a/default",
			"a",
		),
	})
	view.Bindings[0].CredentialRefs = []string{"credential-a"}
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)

	authority, err := newIntegrationServingAuthority(view)
	if err != nil {
		t.Fatal(err)
	}
	if len(authority.bindings) != 1 || authority.bindings[0].Kind != catalogv1.BindingConnectorCalloutProfile ||
		authority.bindings[0].BusinessKey != "connector-a/default" || authority.bindings[0].DefinitionVersion != 1 {
		t.Fatalf("retained binding identities = %#v", authority.bindings)
	}
	view.Bindings[0].BusinessKey = "connector-a/mutated"
	view.Bindings[0].CredentialRefs[0] = "credential-mutated"
	if authority.bindings[0].BusinessKey != "connector-a/default" ||
		authority.bindings[0].CredentialRefs[0] != "credential-a" {
		t.Fatalf("retained binding identity aliases caller input: %#v", authority.bindings[0])
	}
}

func TestIntegrationServingAuthorityRejectsUnsupportedRuntimeCommand(t *testing.T) {
	for name, mutate := range map[string]func(*catalogv1.RuntimeCommandBinding){
		"kind": func(binding *catalogv1.RuntimeCommandBinding) {
			binding.CommandKind = "runtime.other.execute"
		},
		"payload version": func(binding *catalogv1.RuntimeCommandBinding) {
			binding.PayloadFormatVersion = 99
		},
	} {
		t.Run(name, func(t *testing.T) {
			view := integrationServingTestView([]catalogv1.IntegrationBinding{
				integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
			})
			setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
			mutate(view.Bindings[0].RuntimeCommand)
			view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
			if _, err := newIntegrationServingAuthority(view); err == nil || !strings.Contains(err.Error(), "unsupported Runtime command") {
				t.Fatalf("unsupported Runtime command error = %v", err)
			}
		})
	}
}

func TestIntegrationServingAuthorityAcceptsCatalogSelectedV2Command(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(
			catalogv1.BindingConnectorInboundRoute,
			"connector-a",
			"install-a",
			"connector-a/inbound",
			"a",
		),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	view.Bindings[0].RuntimeCommand.PayloadFormatVersion =
		flowcommandv2.PayloadFormatVersion
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)

	authority, err := newIntegrationServingAuthority(view)
	if err != nil {
		t.Fatal(err)
	}
	route := authority.routes[integrationRouteKey(
		"connector-a", "install-a", "inbound",
	)]
	if route.runtimeCommand.PayloadFormatVersion !=
		flowcommandv2.PayloadFormatVersion {
		t.Fatalf("selected Runtime command = %#v", route.runtimeCommand)
	}
}

func TestIntegrationServingAuthorityRejectsMixedPackGeneration(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorCalloutProfile, "connector-a", "install-a", "connector-a/default", "b"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	sortIntegrationServingBindings(view.Bindings)
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	if _, err := newIntegrationServingAuthority(view); err == nil || !strings.Contains(err.Error(), "mixes payload digests") {
		t.Fatalf("mixed generation error = %v", err)
	}
}

func TestIntegrationServingAuthorityRejectsMultipleInstallationsForOneOwner(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorFieldMapping, "connector-a", "install-b", "connector-a/mapping", "b"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	sortIntegrationServingBindings(view.Bindings)
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	if _, err := newIntegrationServingAuthority(view); err == nil || !strings.Contains(err.Error(), "mixes pack generations") {
		t.Fatalf("mixed installation error = %v", err)
	}
}

func TestIntegrationServingAuthorityPreservesCaseDistinctOwnerIdentities(t *testing.T) {
	view := integrationServingTestView([]catalogv1.IntegrationBinding{
		integrationServingTestBinding(catalogv1.BindingConnectorInboundRoute, "connector-a", "install-a", "connector-a/inbound", "a"),
		integrationServingTestBinding(catalogv1.BindingConnectorFieldMapping, "Connector-A", "install-b", "Connector-A/mapping", "b"),
	})
	setIntegrationServingRouteCommand(view.Bindings, "BoundedInbound")
	sortIntegrationServingBindings(view.Bindings)
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	if _, err := newIntegrationServingAuthority(view); err != nil {
		t.Fatalf("case-distinct owner identities: %v", err)
	}
}

func integrationServingTestView(bindings []catalogv1.IntegrationBinding) catalogv1.IntegrationBindingView {
	config := Config{
		OrgID: "org-test", AppInstallationID: "01900000-0000-7000-8000-000000000001",
		AuthorizedWorkloadID: "integration-service",
	}
	view := validIntegrationBindingView(config)
	view.Bindings = bindings
	sortIntegrationServingBindings(view.Bindings)
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	return view
}

func integrationServingTestBinding(kind catalogv1.IntegrationBindingKind, owner, installation, businessKey, digestByte string) catalogv1.IntegrationBinding {
	return catalogv1.IntegrationBinding{
		Kind: kind, OwnerPack: owner, PackInstallationID: installation,
		PackPayloadDigest: "nexus-pack-sha256-v1:" + strings.Repeat(digestByte, 64),
		BusinessKey:       businessKey, DefinitionVersion: 1, CredentialRefs: []string{},
	}
}

func sortIntegrationServingBindings(bindings []catalogv1.IntegrationBinding) {
	sort.Slice(bindings, func(i, j int) bool {
		left := string(bindings[i].Kind) + "\x00" + bindings[i].OwnerPack + "\x00" + bindings[i].PackInstallationID + "\x00" + bindings[i].BusinessKey
		right := string(bindings[j].Kind) + "\x00" + bindings[j].OwnerPack + "\x00" + bindings[j].PackInstallationID + "\x00" + bindings[j].BusinessKey
		return left < right
	})
}

func setIntegrationServingRouteCommand(bindings []catalogv1.IntegrationBinding, definitionRef string) {
	for index := range bindings {
		if bindings[index].Kind == catalogv1.BindingConnectorInboundRoute {
			bindings[index].RuntimeCommand = &catalogv1.RuntimeCommandBinding{
				CommandKind: flowcommandv1.CommandKind, PayloadFormatVersion: flowcommandv1.PayloadFormatVersion,
				DefinitionRef: definitionRef, DefinitionVersion: "flow-v1",
			}
		}
	}
}

var _ connectorrouting.RouteResolver = (*integrationRouteResolverProbe)(nil)
