package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// AppCallbackSecretView is a one-time plaintext response. It must never be
// embedded in App installation views, approval requests, or diagnostics.
type AppCallbackSecretView struct {
	AppID                 string
	AppSlug               string
	CallbackSigningSecret string
}

// RotateAppCallbackSecretForUser generates a new callback HMAC secret for an
// installed callback App. Direct secret mutation is restricted to current
// workspace owners/admins or an active App manager.
func (store *Store) RotateAppCallbackSecretForUser(
	ctx context.Context,
	userID string,
	workspaceID string,
	installationID string,
) (AppCallbackSecretView, error) {
	workspaceMember, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return AppCallbackSecretView{}, err
	}
	summary, err := store.requireActiveAppRuntimeInstallation(ctx, workspaceID, installationID, nil)
	if err != nil {
		return AppCallbackSecretView{}, err
	}
	if err := store.authorizeAppInstallationManagement(ctx, store.db, workspaceID, workspaceMember, summary.Definition.ID); err != nil {
		return AppCallbackSecretView{}, err
	}
	enabled, err := pinnedAppTransportEnabled(summary.Installation, serviceactor.RuntimeTransportKindCallback)
	if err != nil {
		return AppCallbackSecretView{}, err
	}
	if !enabled {
		return AppCallbackSecretView{}, badRequest("App callback transport is not enabled")
	}
	secret, err := secrets.NewToken(32)
	if err != nil {
		return AppCallbackSecretView{}, fmt.Errorf("generate App callback signing secret: %w", err)
	}

	if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		if err := installation.LockAppInstallationMutationScope(ctx, tx, summary.Definition.ID); err != nil {
			return err
		}
		if _, err := store.revalidateAppInstallationManagementForUpdate(
			ctx,
			tx,
			userID,
			workspaceID,
			workspaceMember.Member.ID,
			summary.Definition.ID,
		); err != nil {
			return err
		}
		var marker int
		if err := tx.QueryRow(ctx, `
SELECT 1
FROM service_installations
WHERE id = $1
  AND workspace_id = $2
  AND service_definition_id = $3
  AND service_kind = $4
  AND status = $5
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(installationID), strings.TrimSpace(workspaceID), summary.Definition.ID, serviceactor.KindApp, serviceactor.StatusActive).Scan(&marker); err != nil {
			if coredb.IsNotFound(err) {
				return ErrNotFound
			}
			return fmt.Errorf("lock App callback installation: %w", err)
		}
		associatedWorkspaceCount, err := appDefinitionAssociatedWorkspaceCount(ctx, tx, summary.Definition.ID)
		if err != nil {
			return err
		}
		if associatedWorkspaceCount > 1 {
			return conflict("This callback App is associated with multiple workspaces, so its shared secret cannot be rotated here.")
		}
		now := time.Now().UTC()
		return store.setAppCallbackSigningSecretInTx(ctx, tx, summary.Definition.ID, secret, now)
	}); err != nil {
		return AppCallbackSecretView{}, err
	}
	return AppCallbackSecretView{
		AppID:                 strings.TrimSpace(summary.Definition.ID),
		AppSlug:               strings.TrimSpace(summary.Definition.Slug),
		CallbackSigningSecret: secret,
	}, nil
}

func (store *Store) setAppCallbackSigningSecretInTx(
	ctx context.Context,
	tx coredb.Tx,
	definitionID string,
	secret string,
	now time.Time,
) error {
	result, err := tx.Exec(ctx, `
UPDATE service_definitions
SET config = JSON_SET(COALESCE(config, JSON_OBJECT()), '$.callbackSigningSecret', $1),
    updated_at = $2
WHERE id = $3
  AND service_kind = 'app'
`, strings.TrimSpace(secret), now.UTC(), strings.TrimSpace(definitionID))
	if err != nil {
		return fmt.Errorf("rotate App callback signing secret: %w", err)
	}
	if result.RowsAffected() != 1 {
		return ErrNotFound
	}
	definition, err := store.syncAppServiceDefinition(ctx, tx, definitionID, now)
	if err != nil {
		return err
	}
	if strings.TrimSpace(definition.ID) != strings.TrimSpace(definitionID) {
		return conflict("App callback definition changed.")
	}
	return nil
}
