package app

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

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

const serviceEventSubscriptionFailureCodeProviderConnectionDown = "provider_connection_disabled"

// BlockServiceEventSubscriptions mutates the canonical service-event
// subscription ledger when a provider connection is unavailable.
func (store *Store) BlockServiceEventSubscriptions(ctx context.Context, workspaceID, providerName, connectionRef string) (int, error) {
	return store.mutateServiceEventSubscriptionConnectionBlock(ctx, workspaceID, providerName, connectionRef, true)
}

// UnblockServiceEventSubscriptions restores canonical service-event
// subscriptions after a provider connection recovers.
func (store *Store) UnblockServiceEventSubscriptions(ctx context.Context, workspaceID, providerName, connectionRef string) (int, error) {
	return store.mutateServiceEventSubscriptionConnectionBlock(ctx, workspaceID, providerName, connectionRef, false)
}

func (store *Store) mutateServiceEventSubscriptionConnectionBlock(ctx context.Context, workspaceID, providerName, connectionRef string, block bool) (int, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	if trimmedWorkspaceID == "" {
		return 0, badRequest("workspaceId is required")
	}
	provider := normalizePullRequestSubscriptionProvider(providerName)
	serviceDefinitionID, err := pullRequestSubscriptionProviderAppID(provider)
	if err != nil {
		return 0, err
	}
	trimmedConnectionRef := normalizePullRequestSubscriptionConnectionRef(provider, connectionRef)

	mutatedCount := 0
	if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		subscriptions, err := loadServiceEventSubscriptionsForConnectionBlock(ctx, tx, trimmedWorkspaceID, serviceDefinitionID)
		if err != nil {
			return err
		}
		now := time.Now().UTC()
		for _, subscription := range subscriptions {
			metadata := decodeServiceEventSubscriptionJSONMap(subscription.Metadata)
			storedConnectionRef := serviceEventSubscriptionStoredConnectionRef(subscription, metadata)
			if block && trimmedConnectionRef != "" && normalizePullRequestSubscriptionConnectionRef(provider, storedConnectionRef) != trimmedConnectionRef {
				continue
			}
			unblockConnectionRef := storedConnectionRef
			if block && unblockConnectionRef == "" {
				unblockConnectionRef = trimmedConnectionRef
			}
			if !block && subscription.Status == ServiceEventSubscriptionStatusBlocked {
				var ready bool
				unblockConnectionRef, ready, err = store.resolvePullRequestSubscriptionConnectionForUnblock(ctx, provider, trimmedWorkspaceID, subscription, metadata, trimmedConnectionRef)
				if err != nil {
					return err
				}
				if !ready {
					if err := updateServiceEventSubscriptionBlockedFailure(ctx, tx, subscription.ID, now, serviceEventSubscriptionFailureCodeProviderConnectionDown, "provider connection is still unavailable"); err != nil {
						return err
					}
					continue
				}
			}
			changed, err := mutateServiceEventSubscriptionConnectionBlockRow(ctx, tx, subscription, metadata, block, now, unblockConnectionRef)
			if err != nil {
				return err
			}
			if changed {
				mutatedCount++
			}
		}
		return nil
	}); err != nil {
		return 0, err
	}
	return mutatedCount, nil
}

func normalizePullRequestSubscriptionConnectionRef(providerName string, connectionRef string) string {
	trimmed := strings.TrimSpace(connectionRef)
	if normalizePullRequestSubscriptionProvider(providerName) == PullRequestSubscriptionProviderGitea {
		return strings.ToLower(trimmed)
	}
	return trimmed
}

func loadServiceEventSubscriptionsForConnectionBlock(ctx context.Context, queryer coredb.Queryer, workspaceID string, serviceDefinitionID string) ([]ServiceEventSubscriptionRecord, error) {
	rows, err := queryer.Query(ctx, serviceEventSubscriptionRecordSelectSQL()+`
WHERE subscription.workspace_id = $1
  AND subscription.service_definition_id = $2
  AND subscription.subscription_kind = $3
  AND subscription.removed_at IS NULL
  AND subscription.status IN ('active', 'paused', 'blocked')
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(serviceDefinitionID), serviceactor.SubscriptionKindPullRequest)
	if err != nil {
		return nil, fmt.Errorf("load service event subscriptions for connection block: %w", err)
	}
	defer rows.Close()
	subscriptions := make([]ServiceEventSubscriptionRecord, 0)
	for rows.Next() {
		subscription, err := scanServiceEventSubscriptionRecord(rows)
		if err != nil {
			return nil, err
		}
		subscriptions = append(subscriptions, subscription)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate service event subscriptions for connection block: %w", err)
	}
	return subscriptions, nil
}

func loadServiceEventSubscriptionsForInstallationBlock(ctx context.Context, queryer coredb.Queryer, workspaceID string, serviceDefinitionID string, installationID string) ([]ServiceEventSubscriptionRecord, error) {
	rows, err := queryer.Query(ctx, serviceEventSubscriptionRecordSelectSQL()+`
WHERE subscription.workspace_id = $1
  AND subscription.service_definition_id = $2
  AND subscription.service_installation_id = $3
  AND subscription.subscription_kind = $4
  AND subscription.removed_at IS NULL
  AND subscription.status IN ('active', 'paused', 'blocked')
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(serviceDefinitionID), strings.TrimSpace(installationID), serviceactor.SubscriptionKindPullRequest)
	if err != nil {
		return nil, fmt.Errorf("load service event subscriptions for connection block: %w", err)
	}
	defer rows.Close()
	subscriptions := make([]ServiceEventSubscriptionRecord, 0)
	for rows.Next() {
		subscription, err := scanServiceEventSubscriptionRecord(rows)
		if err != nil {
			return nil, err
		}
		subscriptions = append(subscriptions, subscription)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate service event subscriptions for connection block: %w", err)
	}
	return subscriptions, nil
}

func (store *Store) resolvePullRequestSubscriptionConnectionForUnblock(ctx context.Context, provider string, workspaceID string, subscription ServiceEventSubscriptionRecord, metadata map[string]any, requestedConnectionRef string) (string, bool, error) {
	repositoryFullName, err := pullRequestSubscriptionRepositoryFullName(provider, subscription.ResourceRef, subscription.ResourceKey)
	if err != nil {
		return "", false, err
	}
	expectedConnectionRef := strings.TrimSpace(requestedConnectionRef)
	if expectedConnectionRef == "" {
		expectedConnectionRef = serviceEventSubscriptionStoredConnectionRef(subscription, metadata)
	}
	connectionRef, status, err := store.resolvePullRequestSubscriptionConnection(ctx, provider, workspaceID, repositoryFullName, expectedConnectionRef)
	if err != nil {
		return "", false, err
	}
	return strings.TrimSpace(connectionRef), status == webhooks.ServiceEventConnectionReady && strings.TrimSpace(connectionRef) != "", nil
}

func serviceEventSubscriptionStoredConnectionRef(subscription ServiceEventSubscriptionRecord, metadata map[string]any) string {
	if trimmed := strings.TrimSpace(subscription.ProviderConnectionRef); trimmed != "" {
		return trimmed
	}
	return strings.TrimSpace(serviceEventSubscriptionMetadataString(metadata, "provider_connection_ref"))
}

func mutateServiceEventSubscriptionConnectionBlockRow(ctx context.Context, execer coredb.QueryExecer, subscription ServiceEventSubscriptionRecord, metadata map[string]any, block bool, now time.Time, connectionRef string) (bool, error) {
	if metadata == nil {
		metadata = map[string]any{}
	}
	if block {
		if subscription.Status == ServiceEventSubscriptionStatusBlocked {
			return false, nil
		}
		metadata["pre_block_status"] = subscription.Status
		if strings.TrimSpace(connectionRef) != "" {
			metadata["provider_connection_ref"] = strings.TrimSpace(connectionRef)
		}
		metadataJSON, err := marshalServiceEventSubscriptionJSONMap(metadata)
		if err != nil {
			return false, err
		}
		_, err = execer.Exec(ctx, `
UPDATE service_event_subscriptions
SET status = 'blocked',
    last_failure_at = $2,
    last_failure_code = $3,
    last_failure_detail = $4,
    blocked_at = $2,
    blocked_reason = $4,
    metadata = $5,
    updated_at = $2
WHERE id = $1 AND removed_at IS NULL
`, subscription.ID, now, serviceEventSubscriptionFailureCodeProviderConnectionDown, "provider connection is unavailable", metadataJSON)
		if err != nil {
			return false, fmt.Errorf("block service event subscription: %w", err)
		}
		return true, nil
	}

	if subscription.Status != ServiceEventSubscriptionStatusBlocked {
		return false, nil
	}
	restoredStatus := serviceEventSubscriptionRestoredStatus(metadata)
	trimmedConnectionRef := strings.TrimSpace(connectionRef)
	if trimmedConnectionRef != "" {
		metadata["provider_connection_ref"] = trimmedConnectionRef
	}
	delete(metadata, "pre_block_status")
	metadataJSON, err := marshalServiceEventSubscriptionJSONMap(metadata)
	if err != nil {
		return false, err
	}
	pausedAtUpdate := "paused_at"
	if restoredStatus == ServiceEventSubscriptionStatusActive {
		pausedAtUpdate = "NULL"
	}
	_, err = execer.Exec(ctx, fmt.Sprintf(`
UPDATE service_event_subscriptions
SET status = $2,
    service_connection_ref = $3,
    paused_at = %s,
    last_failure_at = NULL,
    last_failure_code = '',
    last_failure_detail = '',
    blocked_at = NULL,
    blocked_reason = '',
    metadata = $4,
    updated_at = $5
WHERE id = $1 AND removed_at IS NULL
`, pausedAtUpdate), subscription.ID, restoredStatus, trimmedConnectionRef, metadataJSON, now)
	if err != nil {
		return false, fmt.Errorf("unblock service event subscription: %w", err)
	}
	return true, nil
}

func updateServiceEventSubscriptionBlockedFailure(ctx context.Context, execer coredb.QueryExecer, subscriptionID string, now time.Time, code string, message string) error {
	_, err := execer.Exec(ctx, `
UPDATE service_event_subscriptions
SET last_failure_at = $2,
    last_failure_code = $3,
    last_failure_detail = $4,
    updated_at = $2
WHERE id = $1 AND removed_at IS NULL AND status = 'blocked'
`, subscriptionID, now, strings.TrimSpace(code), strings.TrimSpace(message))
	if err != nil {
		return fmt.Errorf("update blocked service event subscription failure: %w", err)
	}
	return nil
}

func serviceEventSubscriptionRestoredStatus(metadata map[string]any) string {
	switch serviceEventSubscriptionMetadataString(metadata, "pre_block_status") {
	case ServiceEventSubscriptionStatusPaused:
		return ServiceEventSubscriptionStatusPaused
	default:
		return ServiceEventSubscriptionStatusActive
	}
}

func serviceEventSubscriptionMetadataString(metadata map[string]any, key string) string {
	if metadata == nil {
		return ""
	}
	value, ok := metadata[key]
	if !ok {
		return ""
	}
	switch typed := value.(type) {
	case string:
		return strings.TrimSpace(typed)
	case fmt.Stringer:
		return strings.TrimSpace(typed.String())
	default:
		return ""
	}
}
