package auth

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
)

// OAuthDisconnectAuditRecorder records the actor, self-service target, and
// revocation counts in the same transaction as a connection disconnect.
type OAuthDisconnectAuditRecorder interface {
	RecordSetupChange(ctx context.Context, action, section, display, userID string) error
}

// ListUserConnections exposes the caller's currently live authorization-client
// relationships. _System_OAuthConsent remains the sole connection authority;
// sessions and refresh tokens are never used to infer this state.
func (s *OAuthService) ListUserConnections(ctx context.Context, userID string) ([]ports.AuthorizationGrantConnection, error) {
	userID = strings.TrimSpace(userID)
	if userID == "" {
		return nil, errors.NewValidationError("user", "user is required")
	}
	grants, err := s.requireAuthorizationGrantRepository()
	if err != nil {
		return nil, err
	}
	return grants.ListLiveAuthorizationGrantConnections(ctx, userID)
}

// DisconnectUserConnection atomically revokes the user's authorization grant
// and every credential it can still use. The SystemRepository locks the client
// before the grant adapter locks the user, preserving app -> user -> grant for
// disconnect and concurrent issuance alike. A repeated disconnect is a safe
// no-op: it cannot create or revive a grant.
func (s *OAuthService) DisconnectUserConnection(ctx context.Context, userID, clientID string) error {
	userID = strings.TrimSpace(userID)
	clientID = strings.TrimSpace(clientID)
	if userID == "" {
		return errors.NewValidationError("user", "user is required")
	}
	if clientID == "" {
		return errors.NewValidationError("connected_app", "connected app id is required")
	}
	if s.systemRepo == nil || s.oauthRepo == nil || s.sessionRepo == nil {
		return errors.NewInternalError("OAuth connection service is not configured", nil)
	}
	if s.disconnectAudit == nil {
		return errors.NewInternalError("OAuth connection audit is not configured", nil)
	}
	grants, err := s.requireAuthorizationGrantRepository()
	if err != nil {
		return err
	}
	return s.systemRepo.WithConnectedAppCredentialIssuance(ctx, clientID, func(txCtx context.Context) error {
		lockedCtx := ports.WithAuthorizationClientLock(txCtx, clientID)
		revokedGrants, err := grants.RevokeAuthorizationGrantsForSubjectAndClient(lockedCtx, userID, clientID, "user:disconnect")
		if err != nil {
			return err
		}
		consumedUnboundCodes, err := grants.ConsumeUnboundOAuthCodesForSubject(lockedCtx, userID, clientID)
		if err != nil {
			return err
		}
		revokedRefreshTokens, err := s.oauthRepo.RevokeRefreshTokensForUserAndConnectedApp(lockedCtx, userID, clientID)
		if err != nil {
			return err
		}
		revokedSessions, err := s.sessionRepo.RevokeSessionsForUserAndConnectedApp(lockedCtx, userID, clientID)
		if err != nil {
			return err
		}
		return s.disconnectAudit.RecordSetupChange(
			lockedCtx,
			"Disconnect OAuth connection",
			"Connected App",
			fmt.Sprintf("actor=%s target_user=%s connected_app=%s revoked_grants=%d consumed_unbound_codes=%d revoked_refresh_tokens=%d revoked_sessions=%d", userID, userID, clientID, revokedGrants, consumedUnboundCodes, revokedRefreshTokens, revokedSessions),
			userID,
		)
	})
}
