package app

import (
	"context"
	"crypto/sha256"
	"crypto/subtle"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// AppNotificationInternalPrincipal is the authenticated, allowlisted
// first-party execution identity projected by the trusted-internal ingress.
type AppNotificationInternalPrincipal struct {
	IdentityID            string
	ServiceDefinitionSlug string
	WorkspaceID           string
	ServiceInstallationID string
	ServiceMemberID       string
	service               appNotificationInternalService
}

// RegisterAppNotificationRecipientResolver registers one owning-domain
// recipient-basis resolver. Registration is process-local because the durable
// recipient fact remains owned by that domain.
func (store *Store) RegisterAppNotificationRecipientResolver(kind string, resolver AppNotificationRecipientResolver) error {
	trimmedKind := strings.TrimSpace(kind)
	if trimmedKind == "" || resolver == nil {
		return fmt.Errorf("App notification recipient kind and resolver are required")
	}
	store.appNotificationRegistryMu.Lock()
	defer store.appNotificationRegistryMu.Unlock()
	if _, ok := store.appNotificationRecipients[trimmedKind]; ok {
		return fmt.Errorf("App notification recipient resolver %q is already registered", trimmedKind)
	}
	store.appNotificationRecipients[trimmedKind] = resolver
	return nil
}

// RegisterAppNotificationInternalService adds or idempotently reuses one
// trusted first-party service registration.
func (store *Store) RegisterAppNotificationInternalService(registration AppNotificationInternalServiceRegistration) error {
	normalized, err := normalizeAppNotificationInternalService(registration)
	if err != nil {
		return err
	}
	return store.registerAppNotificationService(normalized)
}

// RegisterAppNotificationBuiltinService registers one AgentTeam-owned in-process
// publisher/action-handler identity without creating an HTTP credential.
func (store *Store) RegisterAppNotificationBuiltinService(registration AppNotificationBuiltinServiceRegistration) error {
	normalized, err := normalizeAppNotificationBuiltinService(registration)
	if err != nil {
		return err
	}
	return store.registerAppNotificationService(normalized)
}

func (store *Store) registerAppNotificationService(normalized appNotificationInternalService) error {
	store.appNotificationRegistryMu.Lock()
	defer store.appNotificationRegistryMu.Unlock()
	if current, ok := store.appNotificationInternalServices[normalized.IdentityID]; ok {
		if current.SecretDigest != normalized.SecretDigest || current.HasSecret != normalized.HasSecret || current.ServiceDefinitionSlug != normalized.ServiceDefinitionSlug || current.ActionHandlerKey != normalized.ActionHandlerKey ||
			strings.Join(sortedStringSet(current.AllowedBusinessNamespaces), "\x00") != strings.Join(sortedStringSet(normalized.AllowedBusinessNamespaces), "\x00") ||
			strings.Join(sortedStringSet(current.AllowedTargetKinds), "\x00") != strings.Join(sortedStringSet(normalized.AllowedTargetKinds), "\x00") ||
			strings.Join(sortedStringSet(current.AllowedContentKinds), "\x00") != strings.Join(sortedStringSet(normalized.AllowedContentKinds), "\x00") {
			return fmt.Errorf("internal App notification service %q is already registered with another contract", normalized.IdentityID)
		}
		return nil
	}
	if normalized.HasSecret {
		for _, current := range store.appNotificationInternalServices {
			if current.HasSecret && subtle.ConstantTimeCompare(current.SecretDigest[:], normalized.SecretDigest[:]) == 1 {
				return fmt.Errorf("internal App notification service secret is already registered")
			}
		}
	}
	store.appNotificationInternalServices[normalized.IdentityID] = normalized
	return nil
}

func (store *Store) appNotificationRecipientResolver(kind string) (AppNotificationRecipientResolver, bool) {
	store.appNotificationRegistryMu.RLock()
	defer store.appNotificationRegistryMu.RUnlock()
	resolver, ok := store.appNotificationRecipients[strings.TrimSpace(kind)]
	return resolver, ok
}

// AuthenticateAppNotificationInternalService verifies a registered service
// secret in constant time, resolves its active App installation inside the
// addressed workspace, and returns the bounded principal contract.
func (store *Store) AuthenticateAppNotificationInternalService(ctx context.Context, workspaceID, secret string) (AppNotificationInternalPrincipal, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedSecret := strings.TrimSpace(secret)
	if trimmedWorkspaceID == "" || trimmedSecret == "" {
		return AppNotificationInternalPrincipal{}, ErrUnauthorized
	}
	presented := sha256.Sum256([]byte(trimmedSecret))
	store.appNotificationRegistryMu.RLock()
	var matched appNotificationInternalService
	matchedCount := 0
	for _, registered := range store.appNotificationInternalServices {
		if registered.HasSecret && subtle.ConstantTimeCompare(presented[:], registered.SecretDigest[:]) == 1 {
			matched = registered
			matchedCount++
		}
	}
	store.appNotificationRegistryMu.RUnlock()
	if matchedCount != 1 {
		return AppNotificationInternalPrincipal{}, ErrUnauthorized
	}

	return store.resolveAppNotificationInternalPrincipal(ctx, trimmedWorkspaceID, matched)
}

// AppNotificationBuiltinPrincipal resolves a registered AgentTeam-owned identity
// directly for in-process domain code, without a loopback HTTP credential.
func (store *Store) AppNotificationBuiltinPrincipal(ctx context.Context, workspaceID, identityID string) (AppNotificationInternalPrincipal, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedIdentityID := strings.TrimSpace(identityID)
	if trimmedWorkspaceID == "" || trimmedIdentityID == "" {
		return AppNotificationInternalPrincipal{}, ErrUnauthorized
	}
	store.appNotificationRegistryMu.RLock()
	registered, ok := store.appNotificationInternalServices[trimmedIdentityID]
	store.appNotificationRegistryMu.RUnlock()
	if !ok || strings.TrimSpace(registered.ActionHandlerKey) == "" {
		return AppNotificationInternalPrincipal{}, ErrUnauthorized
	}
	return store.resolveAppNotificationInternalPrincipal(ctx, trimmedWorkspaceID, registered)
}

func (store *Store) resolveAppNotificationInternalPrincipal(ctx context.Context, workspaceID string, matched appNotificationInternalService) (AppNotificationInternalPrincipal, error) {
	var installationID string
	var serviceMemberID string
	if err := store.db.QueryRow(ctx, `
SELECT installation.id, service_member.id
FROM service_definitions definition
JOIN service_installations installation
  ON installation.service_definition_id = definition.id
 AND installation.service_kind = definition.service_kind
 AND installation.workspace_id = $1
 AND installation.status = 'active'
JOIN service_members service_member
  ON service_member.service_installation_id = installation.id
 AND service_member.service_kind = installation.service_kind
 AND service_member.status = 'active'
WHERE definition.slug = $2
  AND definition.service_kind = $3
  AND definition.status = 'active'
ORDER BY installation.created_at ASC, installation.id ASC
LIMIT 1
`, strings.TrimSpace(workspaceID), matched.ServiceDefinitionSlug, serviceactor.KindApp).Scan(&installationID, &serviceMemberID); err != nil {
		if coredb.IsNotFound(err) {
			return AppNotificationInternalPrincipal{}, ErrUnauthorized
		}
		return AppNotificationInternalPrincipal{}, fmt.Errorf("resolve internal App notification installation: %w", err)
	}
	return AppNotificationInternalPrincipal{
		IdentityID:            matched.IdentityID,
		ServiceDefinitionSlug: matched.ServiceDefinitionSlug,
		WorkspaceID:           strings.TrimSpace(workspaceID),
		ServiceInstallationID: strings.TrimSpace(installationID),
		ServiceMemberID:       strings.TrimSpace(serviceMemberID),
		service:               matched,
	}, nil
}

func (principal AppNotificationInternalPrincipal) authorize(command AppNotificationCommand) error {
	if strings.TrimSpace(command.WorkspaceID) != strings.TrimSpace(principal.WorkspaceID) || strings.TrimSpace(command.ServiceInstallationID) != strings.TrimSpace(principal.ServiceInstallationID) {
		return ErrForbidden
	}
	if _, ok := principal.service.AllowedTargetKinds[strings.TrimSpace(command.Target.Kind)]; !ok {
		return forbidden("trusted internal service is not allowed to use this App notification target")
	}
	if _, ok := principal.service.AllowedContentKinds[strings.TrimSpace(command.Content.Kind)]; !ok {
		return forbidden("trusted internal service is not allowed to use this App notification content")
	}
	namespace := strings.TrimSpace(command.BusinessObject.Namespace)
	if namespace != "" {
		if _, ok := principal.service.AllowedBusinessNamespaces[namespace]; !ok {
			return forbidden("trusted internal service is not allowed to use this business namespace")
		}
	}
	return nil
}

func (principal AppNotificationInternalPrincipal) authorizeNotificationRecord(record AppNotificationRecord) error {
	if strings.TrimSpace(record.WorkspaceID) != strings.TrimSpace(principal.WorkspaceID) || strings.TrimSpace(record.ServiceInstallationID) != strings.TrimSpace(principal.ServiceInstallationID) {
		return ErrNotFound
	}
	if _, ok := principal.service.AllowedTargetKinds[strings.TrimSpace(record.TargetKind)]; !ok {
		return forbidden("trusted internal service is not allowed to access this App notification target")
	}
	if _, ok := principal.service.AllowedContentKinds[strings.TrimSpace(record.ContentKind)]; !ok {
		return forbidden("trusted internal service is not allowed to access this App notification content")
	}
	if record.BusinessObjectNamespace != nil {
		namespace := strings.TrimSpace(*record.BusinessObjectNamespace)
		if namespace != "" {
			if _, ok := principal.service.AllowedBusinessNamespaces[namespace]; !ok {
				return forbidden("trusted internal service is not allowed to access this business namespace")
			}
		}
	}
	return nil
}
