package connector_outboundintent

import (
	"errors"
	"regexp"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

func TestCatalogMailAuthorityResolvesExactActiveProfileGeneration(t *testing.T) {
	command := testMailCommand(t)
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	expectMailProfileLoad(mock, "mail-oauth-client")
	expectMailProfileLoad(mock, "mail-oauth-client")
	authority, err := NewCatalogMailAuthority(db, mailBindingView(command), []MailBindingDefinition{{
		OwnerPack: "mail-owner", BusinessKey: "mail-owner/delivery", DefinitionVersion: 1,
		ConnectorKey: "connected-mail", ClientCredentialRef: "mail-oauth-client",
		RequiredScopes: []string{"scope.two", "scope.one"},
	}})
	if err != nil {
		t.Fatal(err)
	}
	resolved, err := authority.ResolveResolvedMail(t.Context(), command)
	if err != nil {
		t.Fatal(err)
	}
	binding := resolved.MailBinding
	if binding.ConnectorKey != "connected-mail" || binding.CredentialRef != "mail-oauth-client" ||
		!strings.HasPrefix(resolved.Credential.CanonicalID, "mail-binding/sha256:") ||
		resolved.Credential.Namespace != "mail-owner" ||
		binding.EndpointClass != "mail.send" ||
		strings.Join(binding.RequiredScopes, " ") != "scope.one scope.two" {
		t.Fatalf("binding = %#v", binding)
	}
	if err := authority.RevalidateResolvedMail(t.Context(), resolved); err != nil {
		t.Fatalf("revalidate exact binding: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestCatalogMailAuthorityRevalidationRejectsInactiveExactGeneration(t *testing.T) {
	command := testMailCommand(t)
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	expectMailProfileLoad(mock, "mail-oauth-client")
	expectInactiveMailGeneration(mock)
	authority, err := NewCatalogMailAuthority(db, mailBindingView(command), []MailBindingDefinition{{
		OwnerPack: "mail-owner", BusinessKey: "mail-owner/delivery", DefinitionVersion: 1,
		ConnectorKey: "connected-mail", ClientCredentialRef: "mail-oauth-client",
		RequiredScopes: []string{"mail.send"},
	}})
	if err != nil {
		t.Fatal(err)
	}
	resolved, err := authority.ResolveResolvedMail(t.Context(), command)
	if err != nil {
		t.Fatal(err)
	}
	err = authority.RevalidateResolvedMail(t.Context(), resolved)
	var failure *PreparationError
	if !errors.As(err, &failure) || failure.Code != "mail_binding_unavailable" || failure.Retryable ||
		!errors.Is(failure.Cause, ErrDependency) {
		t.Fatalf("inactive generation error = %#v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestResolvedMailCredentialIdentityIncludesExactCatalogGeneration(t *testing.T) {
	binding := catalogv1.IntegrationBinding{
		Kind:      catalogv1.BindingConnectorCalloutProfile,
		OwnerPack: "mail-owner", PackInstallationID: "install-1",
		PackPayloadDigest: "nexus-pack-sha256-v1:" + strings.Repeat("a", 64),
		BusinessKey:       "mail-owner/delivery", DefinitionVersion: 1,
	}
	definition := MailBindingDefinition{
		OwnerPack: "mail-owner", BusinessKey: "mail-owner/delivery", DefinitionVersion: 1,
		ConnectorKey: "shared-connector-key", ClientCredentialRef: "mail-oauth-client",
		RequiredScopes: []string{"mail.send"},
	}
	first := resolvedMailCredential(binding, definition)
	if first != resolvedMailCredential(binding, definition) || first.Namespace != "mail-owner" {
		t.Fatalf("resolved identity is not deterministic: %#v", first)
	}

	mutations := map[string]func(*catalogv1.IntegrationBinding, *MailBindingDefinition){
		"owner pack": func(binding *catalogv1.IntegrationBinding, definition *MailBindingDefinition) {
			binding.OwnerPack, definition.OwnerPack = "other-owner", "other-owner"
		},
		"pack installation": func(binding *catalogv1.IntegrationBinding, _ *MailBindingDefinition) {
			binding.PackInstallationID = "install-2"
		},
		"pack payload": func(binding *catalogv1.IntegrationBinding, _ *MailBindingDefinition) {
			binding.PackPayloadDigest = "nexus-pack-sha256-v1:" + strings.Repeat("b", 64)
		},
		"business key": func(binding *catalogv1.IntegrationBinding, definition *MailBindingDefinition) {
			binding.BusinessKey, definition.BusinessKey = "mail-owner/other", "mail-owner/other"
		},
		"definition version": func(binding *catalogv1.IntegrationBinding, definition *MailBindingDefinition) {
			binding.DefinitionVersion, definition.DefinitionVersion = 2, 2
		},
		"connector key": func(_ *catalogv1.IntegrationBinding, definition *MailBindingDefinition) {
			definition.ConnectorKey = "other-connector-key"
		},
		"credential ref": func(_ *catalogv1.IntegrationBinding, definition *MailBindingDefinition) {
			definition.ClientCredentialRef = "other-oauth-client"
		},
	}
	for name, mutate := range mutations {
		t.Run(name, func(t *testing.T) {
			candidateBinding, candidateDefinition := binding, definition
			mutate(&candidateBinding, &candidateDefinition)
			if candidate := resolvedMailCredential(candidateBinding, candidateDefinition); candidate.CanonicalID == first.CanonicalID {
				t.Fatalf("%s did not change resolved identity: %#v", name, candidate)
			}
		})
	}
}

func TestCatalogMailAuthorityRejectsProfileCredentialDrift(t *testing.T) {
	command := testMailCommand(t)
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	expectMailProfileLoad(mock, "auxiliary-client")
	authority, err := NewCatalogMailAuthority(db, mailBindingView(command), []MailBindingDefinition{{
		OwnerPack: "mail-owner", BusinessKey: "mail-owner/delivery", DefinitionVersion: 1,
		ConnectorKey: "connected-mail", ClientCredentialRef: "mail-oauth-client",
		RequiredScopes: []string{"mail.send"},
	}})
	if err != nil {
		t.Fatal(err)
	}
	_, err = authority.ResolveMail(t.Context(), command)
	var failure *PreparationError
	if !errors.As(err, &failure) || failure.Code != "mail_binding_mismatch" || failure.Retryable ||
		!errors.Is(failure.Cause, ErrIdentityConflict) {
		t.Fatalf("credential drift error = %#v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestCatalogMailAuthorityRejectsUnpinnedDefinitionsAndCommands(t *testing.T) {
	command := testMailCommand(t)
	view := mailBindingView(command)
	db, _, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	definition := MailBindingDefinition{
		OwnerPack: "mail-owner", BusinessKey: "mail-owner/delivery", DefinitionVersion: 1,
		ConnectorKey: "connected-mail", ClientCredentialRef: "mail-oauth-client",
		RequiredScopes: []string{"mail.send"},
	}
	for name, mutate := range map[string]func(*MailBindingDefinition){
		"noncanonical connector": func(value *MailBindingDefinition) { value.ConnectorKey = " connected-mail" },
		"wrong credential":       func(value *MailBindingDefinition) { value.ClientCredentialRef = "other-client" },
		"duplicate scope":        func(value *MailBindingDefinition) { value.RequiredScopes = []string{"mail.send", "mail.send"} },
	} {
		t.Run(name, func(t *testing.T) {
			candidate := definition
			candidate.RequiredScopes = append([]string(nil), definition.RequiredScopes...)
			mutate(&candidate)
			if _, err := NewCatalogMailAuthority(db, view, []MailBindingDefinition{candidate}); err == nil {
				t.Fatal("NewCatalogMailAuthority accepted invalid definition")
			}
		})
	}
	authority, err := NewCatalogMailAuthority(db, view, []MailBindingDefinition{definition})
	if err != nil {
		t.Fatal(err)
	}
	foreign := command
	foreign.Intent.Bindings = append([]outboundintentv1.BindingReference(nil), command.Intent.Bindings...)
	foreign.Intent.Bindings[0].DefinitionVersion++
	foreign.LinkKey = outboundLinkKey(foreign.Scope, foreign.Intent)
	if _, err := authority.ResolveMail(t.Context(), foreign); !errors.Is(err, ErrInvalidInput) && !errors.Is(err, ErrIdentityConflict) {
		t.Fatalf("foreign command error = %v", err)
	}
	crossRevision := command
	crossRevision.Intent.CatalogRevision++
	if _, err := authority.ResolveMail(t.Context(), crossRevision); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("cross-revision command error = %v", err)
	}
}

func expectMailProfileLoad(mock sqlmock.Sqlmock, credentialRef string) {
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT 1
  FROM _System_PackGeneration
 WHERE PackName = ? AND PackInstallId = ? AND Status = 'active'
   AND AppliedPayloadHash = ? AND DesiredPayloadHash = AppliedPayloadHash
   AND COALESCE(IsDeleted, 0) = 0
 LIMIT 1`)).
		WithArgs("mail-owner", "install-1", "nexus-pack-sha256-v1:"+strings.Repeat("a", 64)).
		WillReturnRows(sqlmock.NewRows([]string{"present"}).AddRow(1))
	mock.ExpectQuery(`SELECT COUNT\(\*\)\s+FROM _System_ConnectorCalloutProfile`).
		WithArgs("mail-owner", "install-1").
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(0))
	rows := sqlmock.NewRows([]string{
		"InstalledPackId", "OwnerPack", "ProfileName", "CredentialRef", "EndpointClass",
		"RetryPolicy", "PageSpec", "BatchSpec", "ObjectPathMap", "Description",
	}).AddRow("install-1", "mail-owner", "delivery", credentialRef, "mail.send", `{}`, `{}`, `{}`, `{}`, "")
	mock.ExpectQuery(`(?s)FROM _System_ConnectorCalloutProfile c\s+WHERE c\.OwnerPack = \?\s+AND c\.InstalledPackId = \?\s+AND c\.ProfileName = \?`).
		WithArgs("mail-owner", "install-1", "delivery").WillReturnRows(rows)
}

func expectInactiveMailGeneration(mock sqlmock.Sqlmock) {
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT 1
  FROM _System_PackGeneration
 WHERE PackName = ? AND PackInstallId = ? AND Status = 'active'
   AND AppliedPayloadHash = ? AND DesiredPayloadHash = AppliedPayloadHash
   AND COALESCE(IsDeleted, 0) = 0
 LIMIT 1`)).
		WithArgs("mail-owner", "install-1", "nexus-pack-sha256-v1:"+strings.Repeat("a", 64)).
		WillReturnRows(sqlmock.NewRows([]string{"present"}))
}

func mailBindingView(command Command) catalogv1.IntegrationBindingView {
	reference := command.Intent.Bindings[0]
	view := catalogv1.IntegrationBindingView{
		OrgID: command.Scope.OrgID, AppInstallationID: command.Scope.AppInstallationID,
		AuthorizedWorkloadID: command.Scope.AuthorizedWorkloadID,
		Lineage: catalogv1.ViewLineage{
			CatalogRevision: command.Scope.CatalogRevision, SchemaGeneration: 1, EngineGeneration: 1,
			SourceSnapshotDigest: "sha256:" + strings.Repeat("b", 64),
		},
		Bindings: []catalogv1.IntegrationBinding{{
			Kind: reference.Kind, OwnerPack: reference.OwnerPack,
			PackInstallationID: reference.PackInstallationID, PackPayloadDigest: reference.PackPayloadDigest,
			BusinessKey: reference.BusinessKey, DefinitionVersion: reference.DefinitionVersion,
			CredentialRefs: []string{"auxiliary-client", "mail-oauth-client"},
		}},
		Receipts: catalogv1.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: catalogv1.IntegrationBindingCapabilityName,
			CatalogRevision: command.Scope.CatalogRevision, SchemaGeneration: 1, EngineGeneration: 1,
		},
	}
	view.Lineage.ViewDigest = catalogv1.ComputeIntegrationBindingViewDigest(view)
	return view
}
