package app

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/app/capabilities"
	"github.com/tidbcloud/AgentTeam/internal/core/webhook"
	"github.com/tidbcloud/AgentTeam/internal/service/appruntime"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

func TestAppCallbackDispatchIssuesUsableNotificationCapability(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	signer, err := capabilities.NewSigner([]byte("app-callback-capability-integration-signing-key"))
	require.NoError(t, err)
	store := New(pool, Options{
		AppCredentialStorageKey: testAppCredentialStorageKey(),
		AppCapabilitySigner:     signer,
	})
	now := time.Now().UTC()
	fixture := seedAppAdminFixture(t, pool, "bot_callback_capability", now)

	type callbackResult struct {
		claims       capabilities.Claims
		principal    AppNotificationRuntimePrincipal
		notification AppNotificationResult
		err          error
	}
	callbackResults := make(chan callbackResult, 1)
	callbackSigningSecret := ""
	var callbackServer *httptest.Server
	callbackServer = httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		result := callbackResult{}
		defer func() {
			callbackResults <- result
		}()
		body, readErr := io.ReadAll(request.Body)
		if readErr != nil {
			result.err = readErr
			http.Error(writer, readErr.Error(), http.StatusInternalServerError)
			return
		}
		if request.Header.Get("AgentTeam-Signature") != webhook.RemoteCallbackSignature(callbackSigningSecret, body) {
			result.err = fmt.Errorf("callback HMAC signature did not match the installed App signing secret")
			http.Error(writer, result.err.Error(), http.StatusUnauthorized)
			return
		}
		var envelope struct {
			DeliveryID      string `json:"delivery_id"`
			AttemptID       string `json:"attempt_id"`
			CapabilityToken string `json:"capability_token"`
		}
		if decodeErr := json.Unmarshal(body, &envelope); decodeErr != nil {
			result.err = decodeErr
			http.Error(writer, decodeErr.Error(), http.StatusBadRequest)
			return
		}
		headerToken := strings.TrimSpace(request.Header.Get("X-AgentTeam-Capability-Token"))
		if headerToken == "" || headerToken != strings.TrimSpace(envelope.CapabilityToken) {
			result.err = fmt.Errorf("callback capability header and envelope do not match")
			http.Error(writer, result.err.Error(), http.StatusUnauthorized)
			return
		}
		result.claims, result.err = signer.Verify(headerToken)
		if result.err != nil {
			http.Error(writer, result.err.Error(), http.StatusUnauthorized)
			return
		}
		result.principal, result.err = store.AuthenticateAppNotificationRuntime(
			request.Context(),
			headerToken,
			[]string{AppRuntimeScopeMessageWrite, AppNotificationScopeWrite},
		)
		if result.err != nil {
			http.Error(writer, result.err.Error(), http.StatusUnauthorized)
			return
		}
		result.notification, result.err = store.CreateAppNotificationForRuntime(request.Context(), result.principal, AppNotificationCommand{
			ClientNotificationID: "callback-delivery:" + strings.TrimSpace(envelope.DeliveryID),
			Target:               AppNotificationTarget{Kind: AppNotificationTargetScope, ScopeID: "chn_bot_callback_capability"},
			Content:              AppNotificationContent{Kind: AppNotificationContentMessage, Text: "Callback capability accepted"},
			CorrelationID:        strings.TrimSpace(envelope.AttemptID),
		})
		if result.err != nil {
			http.Error(writer, result.err.Error(), http.StatusInternalServerError)
			return
		}
		writer.WriteHeader(http.StatusAccepted)
	}))
	defer callbackServer.Close()

	const slug = "app-callback-capability"
	directResult, err := store.InstallCustomAppForWorkspaceAdmin(ctx, fixture.UserID, fixture.WorkspaceID, RequestCustomAppInstallationParams{
		Slug: slug,
		Name: "App Callback Capability",
		Manifest: map[string]any{
			"schema_version": "1",
			"slug":           slug,
			"name":           "App Callback Capability",
			"version":        "1.0.0",
			"callback": map[string]any{
				"url": callbackServer.URL,
			},
			"capabilities": map[string]any{
				"runtime": map[string]any{
					"runtime": map[string]any{
						"transports":        []string{serviceactor.RuntimeTransportKindCallback},
						"default_transport": serviceactor.RuntimeTransportKindCallback,
					},
					"events": []string{registry.AppEventMessageMention},
					"scopes": []string{
						AppMentionScopeRead,
						AppRuntimeScopeMessageWrite,
						AppNotificationScopeWrite,
					},
					"notifications": map[string]any{
						"targets": []string{registry.AppNotificationTargetBoundScope},
						"content": []string{AppNotificationContentMessage},
					},
				},
			},
		},
	})
	require.NoError(t, err)
	callbackSigningSecret = directResult.CallbackSigningSecret
	require.NotEmpty(t, callbackSigningSecret)
	installation := directResult.Installation
	require.Contains(t, installation.Grants, AppNotificationScopeWrite)

	const channelID = "chn_bot_callback_capability"
	seedChannelParticipantProjectionForTests(t, pool, fixture.WorkspaceID, channelID, "app-callback-capability", "App callback capability", "private", fixture.MemberID, fixture.MemberID, now)
	_, err = store.AddChannelServiceForUserWithEvent(ctx, fixture.UserID, fixture.WorkspaceID, channelID, installation.MemberID)
	require.NoError(t, err)
	sourceMessage, _, err := store.SendMessageForUser(
		ctx,
		model.User{ID: fixture.UserID, Name: fixture.UserID, DisplayName: "App Operator"},
		fixture.WorkspaceID,
		channelID,
		"@"+installation.Handle+" publish a status notification",
		nil,
	)
	require.NoError(t, err)

	callbackService := appruntime.NewCallbackService(appruntime.CallbackServiceOptions{
		Store:                    store,
		HTTPClient:               callbackServer.Client(),
		AllowInsecureCallbackURL: true,
	})
	dispatched, err := callbackService.DispatchCallbackDeliveries(ctx, appruntime.CallbackDispatchParams{
		WorkspaceID:           fixture.WorkspaceID,
		ServiceInstallationID: installation.ID,
		SubscriptionKind:      serviceactor.SubscriptionKindAppMention,
		Limit:                 1,
	})
	require.NoError(t, err)
	require.Equal(t, 1, dispatched.Delivered)
	require.Equal(t, 0, dispatched.Retried)

	callback := <-callbackResults
	require.NoError(t, callback.err)
	require.Equal(t, installation.ID, callback.claims.Subject)
	require.Equal(t, installation.AppID, callback.claims.AppID)
	require.Equal(t, fixture.WorkspaceID, callback.claims.WorkspaceID)
	require.Equal(t, installation.MemberID, callback.claims.MemberID)
	require.Equal(t, "app-callback:"+dispatched.Results[0].DeliveryID+":"+dispatched.Results[0].AttemptID, callback.claims.RequestID)
	require.Contains(t, callback.claims.Scopes, AppRuntimeScopeMessageWrite)
	require.Contains(t, callback.claims.Scopes, AppNotificationScopeWrite)
	require.Equal(t, AppNotificationRuntimeAuthenticationCapability, callback.principal.AuthenticationKind)
	require.Equal(t, installation.ID, callback.principal.ServiceInstallationID)
	require.Equal(t, AppNotificationStatusActive, callback.notification.Status)
	require.Equal(t, channelID, callback.notification.ScopeID)
	require.NotEqual(t, sourceMessage.ID, callback.notification.MessageID)

	var runnerCredentialCount int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM service_runtime_credentials
WHERE workspace_id = $1
  AND service_installation_id = $2
`, fixture.WorkspaceID, installation.ID).Scan(&runnerCredentialCount))
	require.Zero(t, runnerCredentialCount, "callback notification API access must not require a runner credential")
}
