package connector_outboundintent

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"errors"
	"slices"
	"strconv"
	"strings"

	connector_throttle "github.com/pingcap-inc/nexuscrm-connectors/features/connector/throttle"
	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

// MailBindingDefinition is provider-neutral release configuration keyed to
// one exact Catalog callout-profile binding. It contains only a credential
// reference; credential values remain runtime configuration.
type MailBindingDefinition struct {
	OwnerPack           string   `json:"ownerPack"`
	BusinessKey         string   `json:"businessKey"`
	DefinitionVersion   uint64   `json:"definitionVersion"`
	ConnectorKey        string   `json:"connectorKey"`
	ClientCredentialRef string   `json:"clientCredentialRef"`
	RequiredScopes      []string `json:"requiredScopes"`
}

type catalogMailDefinition struct {
	binding    catalogv1.IntegrationBinding
	definition MailBindingDefinition
}

// CatalogMailAuthority adapts exact Catalog binding identity to the narrow
// MailBinding seam consumed by the durable outbound worker.
type CatalogMailAuthority struct {
	db          *sql.DB
	scope       Scope
	definitions map[string]catalogMailDefinition
	resolved    map[connector_throttle.ResolvedCredential]catalogMailDefinition
}

func NewCatalogMailAuthority(
	db *sql.DB,
	view catalogv1.IntegrationBindingView,
	definitions []MailBindingDefinition,
) (*CatalogMailAuthority, error) {
	if db == nil || catalogv1.ValidateIntegrationBindingView(view) != nil || len(definitions) == 0 {
		return nil, ErrInvalidInput
	}
	view = cloneBindingView(view)
	authority := &CatalogMailAuthority{db: db, scope: Scope{
		OrgID: view.OrgID, AppInstallationID: view.AppInstallationID,
		AuthorizedWorkloadID: view.AuthorizedWorkloadID, CatalogRevision: view.Lineage.CatalogRevision,
	}, definitions: make(map[string]catalogMailDefinition, len(definitions))}
	authority.resolved = make(map[connector_throttle.ResolvedCredential]catalogMailDefinition, len(definitions))
	for _, definition := range definitions {
		if !canonicalMailDefinition(&definition) {
			return nil, ErrInvalidInput
		}
		key := mailDefinitionKey(definition.OwnerPack, definition.BusinessKey)
		if _, duplicate := authority.definitions[key]; duplicate {
			return nil, ErrIdentityConflict
		}
		var selected *catalogv1.IntegrationBinding
		for index := range view.Bindings {
			binding := view.Bindings[index]
			if binding.Kind != catalogv1.BindingConnectorCalloutProfile ||
				binding.OwnerPack != definition.OwnerPack || binding.BusinessKey != definition.BusinessKey ||
				binding.DefinitionVersion != definition.DefinitionVersion {
				continue
			}
			if selected != nil {
				return nil, ErrIdentityConflict
			}
			copy := binding
			selected = &copy
		}
		if selected == nil || !containsString(selected.CredentialRefs, definition.ClientCredentialRef) {
			return nil, ErrIdentityConflict
		}
		resolved := catalogMailDefinition{binding: *selected, definition: definition}
		credential := resolvedMailCredential(resolved.binding, resolved.definition)
		if _, duplicate := authority.resolved[credential]; duplicate {
			return nil, ErrIdentityConflict
		}
		authority.definitions[key] = resolved
		authority.resolved[credential] = resolved
	}
	return authority, nil
}

func (authority *CatalogMailAuthority) ResolveMail(ctx context.Context, command Command) (MailBinding, error) {
	resolved, err := authority.resolveMail(ctx, command)
	return resolved.MailBinding, err
}

func (authority *CatalogMailAuthority) ResolveResolvedMail(
	ctx context.Context,
	command Command,
) (ResolvedMailBinding, error) {
	return authority.resolveMail(ctx, command)
}

// RevalidateResolvedMail fences provider I/O against pack deactivation or
// replacement after Prepare. The secret-free credential identity selects the
// same immutable Catalog generation; the database lookup proves that exact
// generation and callout profile are still active immediately before token
// resolution.
func (authority *CatalogMailAuthority) RevalidateResolvedMail(
	ctx context.Context,
	binding ResolvedMailBinding,
) error {
	candidate := binding
	candidate.RequiredScopes = append([]string(nil), binding.RequiredScopes...)
	if authority == nil || authority.db == nil || ctx == nil ||
		!canonicalMailBinding(&candidate.MailBinding) ||
		!canonicalResolvedMailCredential(candidate.Credential) {
		return ErrInvalidInput
	}
	resolved, ok := authority.resolved[candidate.Credential]
	if !ok {
		return &PreparationError{Code: "mail_binding_mismatch", Cause: ErrIdentityConflict}
	}
	profile, err := loadExactProfileAuthority(ctx, authority.db, resolved.binding)
	if err != nil {
		code := "mail_binding_unavailable"
		if errors.Is(err, ErrIdentityConflict) {
			code = "mail_binding_mismatch"
		}
		return &PreparationError{Code: code, Retryable: authorityLoadRetryable(err), Cause: err}
	}
	expected := ResolvedMailBinding{
		MailBinding: MailBinding{
			ConnectorKey: resolved.definition.ConnectorKey, CredentialRef: resolved.definition.ClientCredentialRef,
			EndpointClass:  profile.Profile.EndpointClass,
			RequiredScopes: append([]string(nil), resolved.definition.RequiredScopes...),
		},
		Credential: resolvedMailCredential(resolved.binding, resolved.definition),
	}
	if profile.Profile.CredentialRef != resolved.definition.ClientCredentialRef ||
		candidate.ConnectorKey != expected.ConnectorKey || candidate.CredentialRef != expected.CredentialRef ||
		candidate.EndpointClass != expected.EndpointClass || candidate.Credential != expected.Credential ||
		!slices.Equal(candidate.RequiredScopes, expected.RequiredScopes) {
		return &PreparationError{Code: "mail_binding_mismatch", Cause: ErrIdentityConflict}
	}
	return nil
}

func (authority *CatalogMailAuthority) resolveMail(
	ctx context.Context,
	command Command,
) (ResolvedMailBinding, error) {
	if authority == nil || authority.db == nil || ctx == nil || command.Scope != authority.scope ||
		command.Intent.CatalogRevision != command.Scope.CatalogRevision ||
		command.LinkKey != outboundLinkKey(command.Scope, command.Intent) ||
		outboundintentv1.ValidateIntent(command.Intent) != nil ||
		command.Intent.Operation != outboundintentv1.OperationSendMail || len(command.Intent.Bindings) != 1 {
		return ResolvedMailBinding{}, ErrInvalidInput
	}
	reference := command.Intent.Bindings[0]
	if reference.Kind != catalogv1.BindingConnectorCalloutProfile {
		return ResolvedMailBinding{}, ErrIdentityConflict
	}
	resolved, ok := authority.definitions[mailDefinitionKey(reference.OwnerPack, reference.BusinessKey)]
	if !ok || !sameBindingReference(resolved.binding, reference) {
		return ResolvedMailBinding{}, ErrIdentityConflict
	}
	profile, err := loadExactProfileAuthority(ctx, authority.db, resolved.binding)
	if err != nil {
		code := "mail_binding_unavailable"
		if errors.Is(err, ErrIdentityConflict) {
			code = "mail_binding_mismatch"
		}
		return ResolvedMailBinding{}, &PreparationError{Code: code, Retryable: authorityLoadRetryable(err), Cause: err}
	}
	if profile.Profile.CredentialRef != resolved.definition.ClientCredentialRef {
		return ResolvedMailBinding{}, &PreparationError{Code: "mail_binding_mismatch", Cause: ErrIdentityConflict}
	}
	return ResolvedMailBinding{
		MailBinding: MailBinding{
			ConnectorKey:   resolved.definition.ConnectorKey,
			CredentialRef:  resolved.definition.ClientCredentialRef,
			EndpointClass:  profile.Profile.EndpointClass,
			RequiredScopes: append([]string(nil), resolved.definition.RequiredScopes...),
		},
		Credential: resolvedMailCredential(resolved.binding, resolved.definition),
	}, nil
}

func resolvedMailCredential(
	binding catalogv1.IntegrationBinding,
	definition MailBindingDefinition,
) connector_throttle.ResolvedCredential {
	material := strings.Join([]string{
		string(binding.Kind), binding.OwnerPack, binding.PackInstallationID,
		binding.PackPayloadDigest, binding.BusinessKey,
		strconv.FormatUint(binding.DefinitionVersion, 10),
		definition.ConnectorKey, definition.ClientCredentialRef,
	}, "\x00")
	digest := sha256.Sum256([]byte(material))
	return connector_throttle.ResolvedCredential{
		CanonicalID: "mail-binding/sha256:" + hex.EncodeToString(digest[:]),
		Namespace:   binding.OwnerPack,
	}
}

func canonicalMailDefinition(definition *MailBindingDefinition) bool {
	return definition != nil && definition.DefinitionVersion > 0 &&
		boundedMailBindingValue(definition.OwnerPack, maxMailBindingValueBytes) &&
		boundedMailBindingValue(definition.BusinessKey, maxMailBindingValueBytes) &&
		boundedMailBindingValue(definition.ConnectorKey, maxMailBindingValueBytes) &&
		boundedMailBindingValue(definition.ClientCredentialRef, maxMailBindingValueBytes) &&
		canonicalMailScopes(&definition.RequiredScopes)
}

func mailDefinitionKey(ownerPack, businessKey string) string { return ownerPack + "\x00" + businessKey }

var _ MailAuthority = (*CatalogMailAuthority)(nil)
var _ ResolvedMailAuthority = (*CatalogMailAuthority)(nil)
