package app

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/app/capabilities"
	"github.com/tidbcloud/AgentTeam/internal/store/app/runtime"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// AuthenticateAppNotificationRuntime authenticates an ordinary notification
// resource request without coupling authorization to a delivery transport.
// Runner credentials remain supported for runner-stream Apps. Short-lived App
// capability tokens let callback-only Apps use the same create/get/mutate API.
// Both paths re-read the active installation, App capability, and current
// grants before returning a principal.
func (store *Store) AuthenticateAppNotificationRuntime(ctx context.Context, bearerToken string, requiredScopes []string) (AppNotificationRuntimePrincipal, error) {
	token := strings.TrimSpace(bearerToken)
	if token == "" {
		return AppNotificationRuntimePrincipal{}, ErrUnauthorized
	}
	requiredScopes = runtime.NormalizeCapabilityScopes(requiredScopes)

	runner, runnerErr := store.authenticateAppRunnerCredential(ctx, token)
	if runnerErr == nil {
		if err := store.authorizeAppNotificationRuntimeCapability(ctx, runner.ServiceInstallationID); err != nil {
			return AppNotificationRuntimePrincipal{}, err
		}
		if err := requireAppInstallationScopes(ctx, store.db, runner.ServiceInstallationID, requiredScopes); err != nil {
			return AppNotificationRuntimePrincipal{}, err
		}
		return AppNotificationRuntimePrincipal{
			WorkspaceID:           runner.WorkspaceID,
			ServiceDefinitionID:   runner.ServiceDefinitionID,
			ServiceInstallationID: runner.ServiceInstallationID,
			AuthenticationKind:    AppNotificationRuntimeAuthenticationRunner,
			AuthenticationID:      runner.ID,
		}, nil
	}
	if !errors.Is(runnerErr, ErrUnauthorized) {
		return AppNotificationRuntimePrincipal{}, runnerErr
	}

	if store.appCapabilitySigner == nil {
		return AppNotificationRuntimePrincipal{}, ErrUnauthorized
	}
	claims, err := store.appCapabilitySigner.Verify(token)
	if err != nil {
		if errors.Is(err, capabilities.ErrInvalidToken) || errors.Is(err, capabilities.ErrExpiredToken) {
			return AppNotificationRuntimePrincipal{}, ErrUnauthorized
		}
		return AppNotificationRuntimePrincipal{}, fmt.Errorf("verify App notification App capability token: %w", err)
	}
	authenticated, err := store.AuthenticateAppInstallation(ctx, claims.Subject, token, requiredScopes)
	if err != nil {
		return AppNotificationRuntimePrincipal{}, err
	}
	if err := store.authorizeAppNotificationRuntimeCapability(ctx, authenticated.InstallationID); err != nil {
		return AppNotificationRuntimePrincipal{}, err
	}
	return AppNotificationRuntimePrincipal{
		WorkspaceID:           authenticated.WorkspaceID,
		ServiceDefinitionID:   authenticated.AppID,
		ServiceInstallationID: authenticated.InstallationID,
		AuthenticationKind:    AppNotificationRuntimeAuthenticationCapability,
		AuthenticationID:      claims.TokenID,
	}, nil
}

func (store *Store) authorizeAppNotificationRuntimeCapability(ctx context.Context, serviceInstallationID string) error {
	installation, err := store.serviceActorStore().LoadInstallation(ctx, serviceInstallationID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(installation.ServiceKind) != serviceactor.KindApp || strings.TrimSpace(installation.Status) != serviceactor.StatusActive {
		return forbidden("App notification installation is not active")
	}
	if _, err := requirePinnedRuntimeCapability(installation); err != nil {
		if errors.Is(err, ErrBadRequest) {
			return forbidden("App installation does not expose App capability")
		}
		return err
	}
	return nil
}
