package packs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"sort"
	"strings"

	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/inboundhandlerconfig/configwire"
)

func attachSourceBackedConnectorIdentityExtractors(contracts map[ComponentKind]ComponentKindContract) {
	for kind, extractor := range map[ComponentKind]sourceBackedConnectorIdentityExtractor{
		ComponentKindWebhookSecret:                 sourceBackedWebhookSecretConnectorIdentity,
		ComponentKindConnectorCapability:           sourceBackedConnectorCapabilityIdentity,
		ComponentKindConnectorInboundHandlerConfig: sourceBackedConnectorInboundHandlerConfigIdentity,
	} {
		contract, ok := contracts[kind]
		if !ok {
			continue
		}
		contract.sourceConnectorIdentity = extractor
		contracts[kind] = contract
	}
}

func sourceBackedConnectorIdentityFromComponent(source *ResolvedPackSource, component ManifestComponent) (sourceBackedConnectorComponentIdentity, bool) {
	contract, ok := ComponentKindContractFor(component.Kind)
	if !ok || contract.sourceConnectorIdentity == nil {
		return sourceBackedConnectorComponentIdentity{}, false
	}
	payload, err := sourceBackedComponentPayload(source, component)
	if err != nil {
		// Payload-resolution failures are already reported by the main component
		// pass; connector cross-component validation should not duplicate them.
		return sourceBackedConnectorComponentIdentity{}, false
	}
	return contract.sourceConnectorIdentity(payload)
}

func sourceBackedWebhookSecretConnectorIdentity(payload []byte) (sourceBackedConnectorComponentIdentity, bool) {
	var def webhookSecretDefinition
	if err := json.Unmarshal(payload, &def); err != nil {
		return sourceBackedConnectorComponentIdentity{}, false
	}
	name := strings.ToLower(strings.TrimSpace(def.Name))
	if name == "" {
		return sourceBackedConnectorComponentIdentity{}, false
	}
	return sourceBackedConnectorComponentIdentity{
		Role:          sourceBackedConnectorIdentityWebhookSecret,
		WebhookSecret: name,
	}, true
}

func sourceBackedConnectorCapabilityIdentity(payload []byte) (sourceBackedConnectorComponentIdentity, bool) {
	p, ok := decodeConnectorCapabilityIdentity(payload)
	if !ok || p.Namespace == "" || p.Kind == "" {
		return sourceBackedConnectorComponentIdentity{}, false
	}
	return sourceBackedConnectorComponentIdentity{
		Role:           sourceBackedConnectorIdentityCapability,
		Namespace:      p.Namespace,
		CapabilityKind: p.Kind,
	}, true
}

func sourceBackedConnectorInboundHandlerConfigIdentity(payload []byte) (sourceBackedConnectorComponentIdentity, bool) {
	p, ok := decodeConnectorInboundHandlerConfigIdentity(payload)
	if !ok || p.Namespace == "" || p.CapabilityKind == "" {
		return sourceBackedConnectorComponentIdentity{}, false
	}
	return sourceBackedConnectorComponentIdentity{
		Role:           sourceBackedConnectorIdentityInboundHandlerConfig,
		Namespace:      p.Namespace,
		CapabilityKind: p.CapabilityKind,
		Config:         append([]byte(nil), p.Config...),
	}, true
}

// validateConnectorWebhookCapabilityPair fails closed when a connector pack
// declares one or more webhook_secret components but does not also declare BOTH
// the signature_verifier and envelope_parser connector capabilities. The generic
// inbound webhook dispatcher (#338) requires a webhook secret plus both
// capabilities to process any delivery — a pack missing either capability would
// pass install/verify yet drop every live delivery at runtime (401 missing
// verifier / 500 missing parser). Enforcing this in the shared source validator
// keeps validate-pack, install-pack, verify-pack, and startup reconcile
// consistent: a connector-pack author sees the error at authoring time rather
// than discovering a healthy-looking install that cannot receive webhooks.
//
// Multiple named webhook secrets per connector pack are supported end-to-end
// (the capability interface now threads endpoint identity via SecretName through
// VerifierInput and ParserInput, see #1563). A pack may declare any number of
// webhook_secret components; if a verifier/parser needs endpoint-specific
// behavior, its connector_inbound_handler_config uses an endpointConfigs map
// keyed by webhook_secret Name while still remaining one component/row per
// capability kind. This function only checks the capability pair; separate
// handler-config validation checks the endpointConfigs coverage.
//
// This rule also applies during installed-row re-resolution: the runtime
// production gate requires both declared capabilities before any compiled or
// config-derived handler can dispatch. Accepting a webhook-enabled pack without
// the declarations would make readiness green while inbound delivery fails.
//
// This is a forward authoring requirement; it does not constrain the number of
// named webhook secrets (the substrate is (OwnerPack, Name)-scoped and supports
// multiple). connector_capability namespaces are validated to equal the pack
// name elsewhere, so a presence check over declared kinds is sufficient.
func validateConnectorWebhookCapabilityPair(source *ResolvedPackSource, allowInstalledRowCompat bool) []PackDiagnostic {
	hasSecret := false
	declaredKinds := map[string]struct{}{}
	for _, component := range source.Manifest.Components {
		identity, ok := sourceBackedConnectorIdentityFromComponent(source, component)
		if !ok {
			continue
		}
		if identity.Role == sourceBackedConnectorIdentityWebhookSecret {
			hasSecret = true
			continue
		}
		if identity.Role == sourceBackedConnectorIdentityCapability {
			declaredKinds[strings.TrimSpace(identity.CapabilityKind)] = struct{}{}
		}
	}
	if !hasSecret {
		return nil
	}
	var missing []string
	if _, ok := declaredKinds["signature_verifier"]; !ok {
		missing = append(missing, "signature_verifier")
	}
	if _, ok := declaredKinds["envelope_parser"]; !ok {
		missing = append(missing, "envelope_parser")
	}
	if len(missing) == 0 {
		return nil
	}
	return []PackDiagnostic{diag(
		"connector_pack.webhook_secret.incomplete_capability_pair",
		"components",
		fmt.Sprintf(
			"connector pack declares a webhook_secret but is missing the %s connector capability declaration(s); a webhook-enabled connector pack must declare both signature_verifier and envelope_parser so inbound deliveries can be verified and parsed",
			strings.Join(missing, " and "),
		),
	)}
}

func validateConnectorCapabilityHandlerConfigPair(source *ResolvedPackSource, allowInstalledRowCompat bool) []PackDiagnostic {
	if source == nil || source.Manifest == nil || source.Manifest.Category != ConnectorPack {
		return nil
	}
	// Forward-authoring is strict. Installed-row re-resolution may grandfather a
	// legacy manifest only when this process still has the compiled legacy handler
	// that can keep runtime dispatch alive.

	type declaredConnectorIdentity struct {
		namespace string
		kind      string
		path      string
	}

	var (
		capabilities   []declaredConnectorIdentity
		handlerConfigs []declaredConnectorIdentity
	)
	capabilityKeys := map[string]struct{}{}
	handlerKeys := map[string]struct{}{}

	for i, component := range source.Manifest.Components {
		pathPrefix := fmt.Sprintf("components[%d]", i)
		identity, ok := sourceBackedConnectorIdentityFromComponent(source, component)
		if !ok {
			continue
		}
		if identity.Role == sourceBackedConnectorIdentityCapability {
			declared := declaredConnectorIdentity{
				namespace: identity.Namespace,
				kind:      identity.CapabilityKind,
				path:      pathPrefix,
			}
			capabilities = append(capabilities, declared)
			capabilityKeys[connectorIdentityKey(declared.namespace, declared.kind)] = struct{}{}
			continue
		}
		if identity.Role == sourceBackedConnectorIdentityInboundHandlerConfig {
			declared := declaredConnectorIdentity{
				namespace: identity.Namespace,
				kind:      identity.CapabilityKind,
				path:      pathPrefix,
			}
			handlerConfigs = append(handlerConfigs, declared)
			handlerKeys[connectorIdentityKey(declared.namespace, declared.kind)] = struct{}{}
		}
	}

	var diagnostics []PackDiagnostic
	for _, capability := range capabilities {
		if _, ok := handlerKeys[connectorIdentityKey(capability.namespace, capability.kind)]; ok {
			continue
		}
		if allowInstalledRowCompat && connectorcapabilities.HasLegacyCompiledHandler(capability.namespace, connectorcapabilities.CapabilityKind(capability.kind)) {
			continue
		}
		diagnostics = append(diagnostics, diag(
			"connector_capability.handler_config.missing",
			capability.path,
			fmt.Sprintf("connector_capability %q must declare a same-manifest connector_inbound_handler_config with identical namespace %q and capabilityKind %q", capability.namespace+":"+capability.kind, capability.namespace, capability.kind),
		))
	}
	for _, handlerConfig := range handlerConfigs {
		if _, ok := capabilityKeys[connectorIdentityKey(handlerConfig.namespace, handlerConfig.kind)]; ok {
			continue
		}
		diagnostics = append(diagnostics, diag(
			"connector_inbound_handler_config.capability.missing",
			handlerConfig.path,
			fmt.Sprintf("connector_inbound_handler_config %q is orphaned; declare a same-manifest connector_capability with identical namespace %q and kind %q", handlerConfig.namespace+":"+handlerConfig.kind, handlerConfig.namespace, handlerConfig.kind),
		))
	}
	return diagnostics
}

func validateConnectorInboundHandlerEndpointConfigs(source *ResolvedPackSource) []PackDiagnostic {
	if source == nil || source.Manifest == nil || source.Manifest.Category != ConnectorPack {
		return nil
	}

	declaredSecrets := map[string]struct{}{}
	var secretNames []string
	for _, component := range source.Manifest.Components {
		identity, ok := sourceBackedConnectorIdentityFromComponent(source, component)
		if !ok || identity.Role != sourceBackedConnectorIdentityWebhookSecret {
			continue
		}
		name := strings.ToLower(strings.TrimSpace(identity.WebhookSecret))
		if name == "" {
			continue
		}
		if _, exists := declaredSecrets[name]; exists {
			continue
		}
		declaredSecrets[name] = struct{}{}
		secretNames = append(secretNames, name)
	}
	sort.Strings(secretNames)

	var diagnostics []PackDiagnostic
	for i, component := range source.Manifest.Components {
		identity, ok := sourceBackedConnectorIdentityFromComponent(source, component)
		if !ok || identity.Role != sourceBackedConnectorIdentityInboundHandlerConfig {
			continue
		}
		endpoints, hasEndpointConfigs, err := configwire.EndpointConfigNames(identity.Config)
		if err != nil || !hasEndpointConfigs {
			continue
		}
		endpointSet := make(map[string]struct{}, len(endpoints))
		pathPrefix := fmt.Sprintf("components[%d]", i)
		for _, endpoint := range endpoints {
			endpointSet[endpoint] = struct{}{}
			if _, declared := declaredSecrets[endpoint]; !declared {
				diagnostics = append(diagnostics, diag(
					"connector_inbound_handler_config.endpoint.undeclared_webhook_secret",
					pathPrefix+".payload.config.endpointConfigs",
					fmt.Sprintf("connector_inbound_handler_config %q declares endpoint-specific config for webhook_secret %q, but this connector pack does not declare a matching webhook_secret component", strings.TrimSpace(identity.Namespace)+":"+strings.TrimSpace(identity.CapabilityKind), endpoint),
				))
			}
		}
		for _, secretName := range secretNames {
			if _, covered := endpointSet[secretName]; covered {
				continue
			}
			diagnostics = append(diagnostics, diag(
				"connector_inbound_handler_config.endpoint.missing",
				pathPrefix+".payload.config.endpointConfigs",
				fmt.Sprintf("connector_inbound_handler_config %q uses endpointConfigs but is missing webhook_secret endpoint %q; endpoint-specific configs must cover every declared webhook_secret for this capability", strings.TrimSpace(identity.Namespace)+":"+strings.TrimSpace(identity.CapabilityKind), secretName),
			))
		}
	}
	return diagnostics
}

func validateConnectorCapabilityComponent(manifestName string, component ManifestComponent, payload []byte, pathPrefix string) []PackDiagnostic {
	var diagnostics []PackDiagnostic
	var p ConnectorCapabilityComponentPayload
	if err := json.Unmarshal(payload, &p); err != nil {
		diagnostics = append(diagnostics, diag("connector_capability.payload.invalid", pathPrefix, fmt.Sprintf("connector_capability payload parse failed: %v", err)))
		return diagnostics
	}
	p.Namespace = strings.TrimSpace(p.Namespace)
	p.Kind = strings.TrimSpace(p.Kind)

	if p.Namespace == "" {
		diagnostics = append(diagnostics, diag("connector_capability.namespace.missing", pathPrefix+".payload.namespace", "connector_capability namespace is required"))
	}
	if p.Kind == "" {
		diagnostics = append(diagnostics, diag("connector_capability.kind.missing", pathPrefix+".payload.kind", "connector_capability kind is required"))
	} else if _, ok := connectorCapabilityKinds[p.Kind]; !ok {
		validKinds := connectorCapabilityValidKindsList()
		diagnostics = append(diagnostics, diag("connector_capability.kind.unsupported", pathPrefix+".payload.kind", fmt.Sprintf("connector_capability kind %q is not in the supported set (%s); adding a new kind requires a Connector SDK RFC", p.Kind, validKinds)))
	}
	if p.Namespace != "" && strings.TrimSpace(manifestName) != "" && p.Namespace != strings.TrimSpace(manifestName) {
		diagnostics = append(diagnostics, diag("connector_capability.namespace.mismatch_pack_name", pathPrefix+".payload.namespace", fmt.Sprintf("connector_capability namespace %q must equal the manifest name %q", p.Namespace, strings.TrimSpace(manifestName))))
	}
	// Enforce that the manifest businessKey matches the canonical
	// `namespace:kind` identity. Without this, two components with different
	// manifest businessKeys but the same (namespace, kind) payload would dedupe
	// at the manifest layer but collapse to a single declaration row at
	// install/verify, breaking the fail-closed identity contract per Codex Review.
	if p.Namespace != "" && p.Kind != "" {
		expectedBK := p.Namespace + ":" + p.Kind
		gotBK := strings.TrimSpace(component.BusinessKey)
		if gotBK != expectedBK {
			diagnostics = append(diagnostics, diag("connector_capability.business_key.mismatch_payload", pathPrefix+".businessKey", fmt.Sprintf("connector_capability businessKey %q must equal canonical %q (namespace:kind)", gotBK, expectedBK)))
		}
	}
	if component.Manageability != "" && component.Manageability != ManageabilityLocked {
		diagnostics = append(diagnostics, diag("connector_capability.manageability.invalid", pathPrefix+".manageability", fmt.Sprintf("connector_capability requires manageability %q, got %q", ManageabilityLocked, component.Manageability)))
	}
	return diagnostics
}

func decodeConnectorCapabilityIdentity(payload []byte) (ConnectorCapabilityComponentPayload, bool) {
	var p ConnectorCapabilityComponentPayload
	if err := json.Unmarshal(payload, &p); err != nil {
		return ConnectorCapabilityComponentPayload{}, false
	}
	p.Namespace = strings.TrimSpace(p.Namespace)
	p.Kind = strings.TrimSpace(p.Kind)
	return p, true
}

func decodeConnectorInboundHandlerConfigIdentity(payload []byte) (ConnectorInboundHandlerConfigComponentPayload, bool) {
	var p ConnectorInboundHandlerConfigComponentPayload
	dec := json.NewDecoder(bytes.NewReader(payload))
	dec.DisallowUnknownFields()
	if err := dec.Decode(&p); err != nil {
		return ConnectorInboundHandlerConfigComponentPayload{}, false
	}
	if err := ensureJSONEOF(dec); err != nil {
		return ConnectorInboundHandlerConfigComponentPayload{}, false
	}
	p.Namespace = strings.TrimSpace(p.Namespace)
	p.CapabilityKind = strings.TrimSpace(p.CapabilityKind)
	return p, true
}

func connectorIdentityKey(namespace, kind string) string {
	return strings.TrimSpace(namespace) + "\x00" + strings.TrimSpace(kind)
}

func connectorCapabilityValidKindsList() string {
	kinds := make([]string, 0, len(connectorCapabilityKinds))
	for k := range connectorCapabilityKinds {
		kinds = append(kinds, fmt.Sprintf("%q", k))
	}
	sort.Strings(kinds)
	return strings.Join(kinds, ", ")
}
