package connector_webhooks

import (
	"context"
	"fmt"
	"testing"
	"time"

	connectorcapabilities "github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
	connector_events "github.com/pingcap-inc/nexuscrm-connectors/features/connector/events"
	connector_packstate "github.com/pingcap-inc/nexuscrm-connectors/features/connector/packstate"
)

func TestMaterializeVerifiedDeliveryUsesVerifiedPackIncarnation(t *testing.T) {
	const verifiedPackID = "install-verified"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	var routePackID string
	routes := &capturingRouteResolver{
		version:       7,
		flow:          "SomeHandler",
		capturePackID: func(id string) { routePackID = id },
	}
	resolvePackCalled := false
	var enqueuedPackID string
	d := &Dispatcher{
		resolvePack: func(context.Context, string) (*connector_packstate.ConnectorPackRow, bool, error) {
			resolvePackCalled = true
			return &connector_packstate.ConnectorPackRow{Namespace: testNamespace, InstallID: "install-current"}, true, nil
		},
		registry: buildRegistryForPackInstallID(testNamespace, verifiedPackID, true, nil, parser),
		routes:   routes,
		enqueueBatch: func(_ context.Context, _, packInstallID, _ string, events []connector_events.BatchEvent) ([]string, []bool, error) {
			enqueuedPackID = packInstallID
			return []string{"event-id"}, make([]bool, len(events)), nil
		},
	}

	result, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  verifiedPackID,
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
	})
	if err != nil {
		t.Fatalf("MaterializeVerifiedDelivery: %v", err)
	}
	if resolvePackCalled {
		t.Fatal("MaterializeVerifiedDelivery called active pack resolver; want verified pack incarnation")
	}
	if routePackID != verifiedPackID {
		t.Fatalf("ResolveFlow packInstallID = %q, want %q", routePackID, verifiedPackID)
	}
	if enqueuedPackID != verifiedPackID {
		t.Fatalf("enqueue packInstallID = %q, want %q", enqueuedPackID, verifiedPackID)
	}
	if result.Metadata["materializedPackId"] != verifiedPackID {
		t.Fatalf("materializedPackId = %q, want %q", result.Metadata["materializedPackId"], verifiedPackID)
	}
}

func TestMaterializeVerifiedDeliveryCachesRouteBetweenReceipts(t *testing.T) {
	const verifiedPackID = "install-verified"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	routes := &countingRouteResolver{version: 7, flow: "SomeHandler"}
	enqueued := 0
	d := &Dispatcher{
		registry:   buildRegistryForPackInstallID(testNamespace, verifiedPackID, true, nil, parser),
		routes:     routes,
		routeCache: newMaterializerRouteCache(time.Minute),
		enqueueBatch: func(_ context.Context, _, _ string, _ string, events []connector_events.BatchEvent) ([]string, []bool, error) {
			enqueued += len(events)
			return []string{"event-id"}, make([]bool, len(events)), nil
		},
	}

	for i := 0; i < 2; i++ {
		if _, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
			Namespace:       testNamespace,
			VerifiedPackID:  verifiedPackID,
			VerifiedVersion: "v1",
			SecretName:      testSecretName,
			RawBody:         []byte(`{}`),
		}); err != nil {
			t.Fatalf("MaterializeVerifiedDelivery #%d: %v", i+1, err)
		}
	}
	if routes.resolveFlow != 1 {
		t.Fatalf("ResolveFlow calls = %d, want 1 route cache miss across two same-kind receipts", routes.resolveFlow)
	}
	if enqueued != 2 {
		t.Fatalf("enqueued events = %d, want 2", enqueued)
	}
}

func TestMaterializeVerifiedDeliveryRevalidatesParserCapabilityBetweenReceipts(t *testing.T) {
	const verifiedPackID = "install-verified"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	gateCalls := 0
	gateAvailable := true
	reg := connectorcapabilities.NewRegistry(func(_ context.Context, _ string, kind connectorcapabilities.CapabilityKind, _ string) (bool, error) {
		if kind == connectorcapabilities.EnvelopeParser {
			gateCalls++
		}
		return gateAvailable, nil
	})
	if err := reg.UpsertCapabilityForPackInstall(testNamespace, connectorcapabilities.EnvelopeParser, verifiedPackID, parser); err != nil {
		t.Fatalf("UpsertCapabilityForPackInstall: %v", err)
	}
	enqueued := 0
	d := &Dispatcher{
		registry:           reg,
		routes:             noopRoutes,
		parserSingleflight: newMaterializerParserSingleflight(),
		enqueueBatch: func(_ context.Context, _, _ string, _ string, events []connector_events.BatchEvent) ([]string, []bool, error) {
			enqueued += len(events)
			return []string{"event-id"}, make([]bool, len(events)), nil
		},
	}

	if _, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  verifiedPackID,
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
	}); err != nil {
		t.Fatalf("MaterializeVerifiedDelivery initial: %v", err)
	}

	gateAvailable = false
	_, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  verifiedPackID,
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
	})
	if err == nil {
		t.Fatal("MaterializeVerifiedDelivery after parser gate closed = nil, want pending_config")
	}
	if got := materializerErrorKind(err); got != materializerPendingConfig {
		t.Fatalf("MaterializeVerifiedDelivery error kind = %s, want %s", got, materializerPendingConfig)
	}
	if gateCalls != 2 {
		t.Fatalf("parser capability gate calls = %d, want 2 live checks", gateCalls)
	}
	if parser.calls != 1 {
		t.Fatalf("parser calls = %d, want only the first receipt to reach the stale parser", parser.calls)
	}
	if enqueued != 1 {
		t.Fatalf("enqueued events = %d, want only the first receipt enqueued", enqueued)
	}
}

func TestMaterializeVerifiedDeliveryEvictsCachedRouteAfterFinalGuardFailure(t *testing.T) {
	const verifiedPackID = "install-verified"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	routes := &countingRouteResolver{version: 7, flow: "SomeHandler"}
	enqueueCalls := 0
	d := &Dispatcher{
		registry:   buildRegistryForPackInstallID(testNamespace, verifiedPackID, true, nil, parser),
		routes:     routes,
		routeCache: newMaterializerRouteCache(time.Minute),
		enqueueBatch: func(_ context.Context, _, _ string, _ string, events []connector_events.BatchEvent) ([]string, []bool, error) {
			enqueueCalls++
			if enqueueCalls == 1 {
				return nil, nil, connector_events.ErrPackIncarnationChanged
			}
			return []string{"event-id"}, make([]bool, len(events)), nil
		},
	}

	_, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  verifiedPackID,
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
	})
	if got := materializerErrorKind(err); got != materializerPendingConfig {
		t.Fatalf("first materialize kind = %q, want %q (err=%v)", got, materializerPendingConfig, err)
	}
	if _, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  verifiedPackID,
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
	}); err != nil {
		t.Fatalf("second MaterializeVerifiedDelivery: %v", err)
	}
	if routes.resolveFlow != 2 {
		t.Fatalf("ResolveFlow calls = %d, want cache eviction after final guard failure", routes.resolveFlow)
	}
}

func TestMaterializeVerifiedDeliveryFallsBackToCurrentPackWhenVerifiedProjectionGone(t *testing.T) {
	const currentPackID = "install-current"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	var routePackID string
	routes := &capturingRouteResolver{
		version:       7,
		flow:          "SomeHandler",
		capturePackID: func(id string) { routePackID = id },
	}
	var enqueuedPackID string
	var snapshot MaterializationSnapshot
	d := &Dispatcher{
		resolvePack: func(context.Context, string) (*connector_packstate.ConnectorPackRow, bool, error) {
			return &connector_packstate.ConnectorPackRow{Namespace: testNamespace, InstallID: currentPackID, PackVersion: "v2"}, true, nil
		},
		registry: buildRegistryForPackInstallID(testNamespace, currentPackID, true, nil, parser),
		routes:   routes,
		enqueueBatch: func(_ context.Context, _, packInstallID, _ string, events []connector_events.BatchEvent) ([]string, []bool, error) {
			enqueuedPackID = packInstallID
			return []string{"event-id"}, make([]bool, len(events)), nil
		},
	}

	result, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  "install-verified",
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{}`),
		BeforeEnqueue: func(_ context.Context, got MaterializationSnapshot) error {
			snapshot = got
			return nil
		},
	})
	if err != nil {
		t.Fatalf("MaterializeVerifiedDelivery: %v", err)
	}
	if routePackID != currentPackID {
		t.Fatalf("ResolveFlow packInstallID = %q, want current %q", routePackID, currentPackID)
	}
	if enqueuedPackID != currentPackID {
		t.Fatalf("enqueue packInstallID = %q, want current %q", enqueuedPackID, currentPackID)
	}
	if snapshot.PackInstallID != currentPackID || snapshot.PackVersion != "v2" {
		t.Fatalf("snapshot pack = %q/%q, want %q/v2", snapshot.PackInstallID, snapshot.PackVersion, currentPackID)
	}
	if result.Metadata["materializedPackId"] != currentPackID || result.Metadata["materializedPackVersion"] != "v2" {
		t.Fatalf("result metadata = %#v, want current materialized pack", result.Metadata)
	}
}

func TestMaterializeVerifiedDeliveryFallbackMalformedPayloadIsPendingConfig(t *testing.T) {
	const currentPackID = "install-current"
	parser := &fakeParser{err: fmt.Errorf("replacement parser rejected older receipt shape: %w", connectorcapabilities.ErrMalformedPayload)}
	d := &Dispatcher{
		resolvePack: func(context.Context, string) (*connector_packstate.ConnectorPackRow, bool, error) {
			return &connector_packstate.ConnectorPackRow{Namespace: testNamespace, InstallID: currentPackID, PackVersion: "v2"}, true, nil
		},
		registry: buildRegistryForPackInstallID(testNamespace, currentPackID, true, nil, parser),
		routes:   noopRoutes,
		enqueueBatch: func(context.Context, string, string, string, []connector_events.BatchEvent) ([]string, []bool, error) {
			t.Fatal("fallback malformed payload must not enqueue")
			return nil, nil, nil
		},
	}

	_, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:       testNamespace,
		VerifiedPackID:  "install-verified",
		VerifiedVersion: "v1",
		SecretName:      testSecretName,
		RawBody:         []byte(`{"older":"shape"}`),
	})
	if err == nil {
		t.Fatal("MaterializeVerifiedDelivery fallback malformed payload = nil, want pending config")
	}
	if got := materializerErrorKind(err); got != materializerPendingConfig {
		t.Fatalf("materializerErrorKind = %q, want %q (err=%v)", got, materializerPendingConfig, err)
	}
	if parser.calls != 1 {
		t.Fatalf("parser calls = %d, want fallback parser attempted once", parser.calls)
	}
}

func TestMaterializeVerifiedDeliveryDoesNotFallbackAfterPackPinned(t *testing.T) {
	const currentPackID = "install-current"
	parser := &fakeParser{
		events: []connectorcapabilities.ConnectorEvent{
			{EventType: "contact.created", Namespace: testNamespace, ObjectKind: "contact", SourceID: "src-a", Payload: []byte(`{}`), IdempotencyKey: "k0"},
		},
	}
	resolvePackCalled := false
	d := &Dispatcher{
		resolvePack: func(context.Context, string) (*connector_packstate.ConnectorPackRow, bool, error) {
			resolvePackCalled = true
			return &connector_packstate.ConnectorPackRow{Namespace: testNamespace, InstallID: currentPackID, PackVersion: "v2"}, true, nil
		},
		registry: buildRegistryForPackInstallID(testNamespace, currentPackID, true, nil, parser),
		routes:   noopRoutes,
		enqueueBatch: func(_ context.Context, _, _ string, _ string, _ []connector_events.BatchEvent) ([]string, []bool, error) {
			t.Fatal("pinned missing materialization must not enqueue under current pack")
			return nil, nil, nil
		},
	}

	_, err := d.MaterializeVerifiedDelivery(context.Background(), VerifiedDelivery{
		Namespace:              testNamespace,
		VerifiedPackID:         "install-verified",
		VerifiedVersion:        "v1",
		MaterializePackID:      "install-pinned",
		MaterializePackVersion: "v1-pinned",
		SecretName:             testSecretName,
		RawBody:                []byte(`{}`),
	})
	if err == nil {
		t.Fatal("MaterializeVerifiedDelivery pinned missing pack = nil, want pending config error")
	}
	if got := materializerErrorKind(err); got != materializerPendingConfig {
		t.Fatalf("materializerErrorKind = %q, want %q (err=%v)", got, materializerPendingConfig, err)
	}
	if resolvePackCalled {
		t.Fatal("MaterializeVerifiedDelivery fell back to active pack after materialized pack was pinned")
	}
}
