package app

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

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

const (
	appRuntimeContextDefaultLimit = 50
	appRuntimeContextMaxLimit     = 100
)

// AppRuntimeContextParams describes one App runtime context read for the
// addressed delivery attempt.
type AppRuntimeContextParams struct {
	AttemptID string
	BeforeSeq int64
	Limit     int
}

// ReadAppRuntimeDeliveryContext lists messages visible to the App service member
// in the delivery target using the message read predicate.
func (store *Store) ReadAppRuntimeDeliveryContext(ctx context.Context, auth AppRuntimeDeliveryAuth, params AppRuntimeContextParams) (message.ScopeAPIMessagePage, error) {
	attemptID := strings.TrimSpace(params.AttemptID)
	if attemptID == "" {
		attemptID = strings.TrimSpace(auth.AttemptID)
	}
	if attemptID == "" {
		return message.ScopeAPIMessagePage{}, badRequest("App runtime context attempt is required")
	}
	var page message.ScopeAPIMessagePage
	now := time.Now().UTC()
	if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		current, err := store.authorizeAppRuntimeDeliveryTx(ctx, tx, auth.BearerToken, auth.DeliveryID, attemptID, now)
		if err != nil {
			return err
		}
		if err := authorizeAppRuntimeDeliveryReplay(current, auth); err != nil {
			return err
		}
		if err := requireAppRuntimeActiveCurrentAttempt(current, now); err != nil {
			return err
		}
		memberID, err := requireActiveAppRuntimeTargetBinding(ctx, tx, current.Delivery)
		if err != nil {
			return err
		}
		if err := requireAppInstallationScopes(ctx, tx, current.ServiceInstallationID, []string{AppRuntimeScopeMessageRead, AppRuntimeScopeThreadRead}); err != nil {
			return err
		}
		scopeAPI := message.NewScopeAPIServiceForQueryer(tx)
		page, err = scopeAPI.ListAppMessages(ctx, message.ScopeAPIHistoryRequest{
			WorkspaceID:            current.WorkspaceID,
			ScopeID:                current.Delivery.DeliveryScopeID,
			ViewerMemberID:         memberID,
			ViewerType:             message.ScopeActorService,
			RequestKind:            message.ScopeVisibilityRequestHistory,
			BeforeSeq:              params.BeforeSeq,
			Limit:                  normalizeAppRuntimeContextLimit(params.Limit),
			IncludeReplyThreadRoot: true,
		})
		if err != nil {
			return err
		}
		sourceMessageID := strings.TrimSpace(current.Delivery.EventID)
		if sourceMessageID == "" {
			return nil
		}
		source, ok, err := scopeAPI.LoadPinnedAppMessage(ctx, message.ScopeAPIPinnedMessageRequest{
			WorkspaceID:    current.WorkspaceID,
			MessageID:      sourceMessageID,
			ViewerMemberID: memberID,
			ViewerType:     message.ScopeActorService,
			RequestKind:    message.ScopeVisibilityRequestSourceRef,
		})
		if err != nil {
			return err
		}
		if ok {
			page.Messages = prependAppRuntimeSourceContextMessage(page.Messages, source)
		}
		return nil
	}); err != nil {
		return message.ScopeAPIMessagePage{}, err
	}
	return page, nil
}

func requireActiveAppRuntimeTargetBinding(ctx context.Context, queryer coredb.QueryRower, delivery serviceactor.EventDelivery) (string, error) {
	if strings.TrimSpace(delivery.ServiceTargetBindingID) == "" {
		return "", ErrForbidden
	}
	var memberID string
	if err := queryer.QueryRow(ctx, `
SELECT service_member.member_id
FROM service_target_bindings binding
JOIN service_members service_member
  ON service_member.id = binding.service_member_id
 AND service_member.service_installation_id = binding.service_installation_id
 AND service_member.status = 'active'
JOIN scope_memberships visible_membership
  ON visible_membership.scope_id = binding.delivery_scope_id
 AND visible_membership.member_id = service_member.member_id
 AND visible_membership.visibility = 'public'
 AND visible_membership.removed_at IS NULL
 AND visible_membership.archived_at IS NULL
 AND visible_membership.effective_to_seq IS NULL
WHERE binding.id = $1
  AND binding.workspace_id = $2
  AND binding.service_definition_id = $3
  AND binding.service_installation_id = $4
  AND binding.service_member_id = $5
  AND binding.service_kind = $6
  AND binding.target_kind = $7
  AND binding.target_ref = $8
  AND binding.delivery_scope_id = $9
  AND binding.permission_check_scope_id = $10
  AND binding.status = 'active'
  AND binding.removed_at IS NULL
`, strings.TrimSpace(delivery.ServiceTargetBindingID), strings.TrimSpace(delivery.WorkspaceID), strings.TrimSpace(delivery.ServiceDefinitionID), strings.TrimSpace(delivery.ServiceInstallationID), strings.TrimSpace(delivery.ServiceMemberID), strings.TrimSpace(delivery.ServiceKind), strings.TrimSpace(delivery.TargetKind), strings.TrimSpace(delivery.TargetRef), strings.TrimSpace(delivery.DeliveryScopeID), strings.TrimSpace(delivery.PermissionCheckScopeID)).Scan(&memberID); err != nil {
		if coredb.IsNotFound(err) {
			return "", ErrForbidden
		}
		return "", fmt.Errorf("check App runtime target binding: %w", err)
	}
	return strings.TrimSpace(memberID), nil
}

func normalizeAppRuntimeContextLimit(limit int) int {
	if limit <= 0 {
		return appRuntimeContextDefaultLimit
	}
	if limit > appRuntimeContextMaxLimit {
		return appRuntimeContextMaxLimit
	}
	return limit
}

func prependAppRuntimeSourceContextMessage(messages []message.AppMessage, source message.AppMessage) []message.AppMessage {
	sourceID := strings.TrimSpace(source.ID)
	if sourceID == "" {
		return messages
	}
	for _, existing := range messages {
		if strings.TrimSpace(existing.ID) == sourceID {
			return messages
		}
	}
	result := make([]message.AppMessage, 0, len(messages)+1)
	result = append(result, source)
	result = append(result, messages...)
	return result
}
