package app

import (
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/webhook"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
)

// Render helpers keep provider-owned external text escaping/truncation separate
// from candidate SQL and delivery side effects. They must not resolve targets or
// write service-event runtime state.
func formatServiceEventDeliveryMessage(event provider.ProviderEvent) string {
	if providerEventIsBaseBranchPush(event) {
		return formatServiceEventBaseBranchPushMessage(event)
	}
	var builder strings.Builder
	builder.WriteString(pullRequestSubscriptionProviderDisplayName(event.Provider))
	builder.WriteString(" ")
	builder.WriteString(serviceEventProviderActionLabel(event))
	if actor := sanitizeServiceEventExternalActor(event.ActorLogin); actor != "" {
		builder.WriteString(" by ")
		builder.WriteString(actor)
	}
	if resource := sanitizeServiceEventExternalDisplayText(event.ResourceIdentifier); resource != "" {
		builder.WriteString(" on ")
		builder.WriteString(resource)
	}
	if url := sanitizeServiceEventExternalDisplayText(event.ResourceURL); url != "" {
		builder.WriteString("\n")
		builder.WriteString(url)
	}
	if body := serviceEventExternalBodyBlockquote(event.BodyExcerpt); body != "" {
		builder.WriteString("\n\n")
		builder.WriteString(body)
	}
	return builder.String()
}

func serviceEventProviderActionLabel(event provider.ProviderEvent) string {
	eventKind := sanitizeServiceEventExternalDisplayText(event.EventKind)
	actionKind := sanitizeServiceEventExternalDisplayText(event.ActionKind)
	if actionKind == "" {
		actionKind = "updated"
	}
	switch {
	case strings.Contains(eventKind, "issue_comment"):
		return "received a PR comment"
	case strings.Contains(eventKind, "review_comment"):
		return "received a PR review comment"
	case strings.Contains(eventKind, "review_submitted"):
		return "received a PR review (" + actionKind + ")"
	case strings.Contains(eventKind, "synchronized"):
		return "synchronized a PR" + serviceEventSynchronizedCommitSuffix(event)
	case strings.Contains(eventKind, "base_branch_pushed"):
		return "updated a PR base branch"
	case strings.Contains(eventKind, "ci_status"):
		return "reported PR CI status (" + actionKind + ")"
	case strings.Contains(eventKind, "workflow_run_status"):
		return "reported PR workflow status (" + actionKind + ")"
	case strings.Contains(eventKind, "workflow_job_status"):
		return "reported PR workflow job status (" + actionKind + ")"
	case strings.Contains(eventKind, "closed"):
		return "closed a PR (" + actionKind + ")"
	case strings.Contains(eventKind, "reopened"):
		return "reopened a PR"
	default:
		return "received a provider event (" + firstNonEmpty(eventKind, actionKind) + ")"
	}
}

func serviceEventSynchronizedCommitSuffix(event provider.ProviderEvent) string {
	shortSHA := serviceEventShortCommitSHA(event.Extra["head_sha"])
	if shortSHA == "" {
		return ""
	}
	return " (commit " + shortSHA + ")"
}

func serviceEventShortCommitSHA(value string) string {
	trimmed := sanitizeServiceEventExternalDisplayText(value)
	if trimmed == "" {
		return ""
	}
	const shortCommitLength = 7
	if len(trimmed) > shortCommitLength {
		return trimmed[:shortCommitLength]
	}
	return trimmed
}

func providerEventIsBaseBranchPush(event provider.ProviderEvent) bool {
	return strings.Contains(strings.ToLower(strings.TrimSpace(event.EventKind)), "base_branch_pushed")
}

func providerEventIsCommitStatus(event provider.ProviderEvent) bool {
	return strings.Contains(strings.ToLower(strings.TrimSpace(event.EventKind)), "ci_status")
}

func providerEventUsesCandidatePullRequest(event provider.ProviderEvent) bool {
	return providerEventIsBaseBranchPush(event) || providerEventIsCommitStatus(event)
}

func formatServiceEventBaseBranchPushMessage(event provider.ProviderEvent) string {
	branch := sanitizeServiceEventExternalDisplayText(firstNonEmpty(event.Extra["base_branch"], event.Extra["branch"]))
	var builder strings.Builder
	builder.WriteString(pullRequestSubscriptionProviderDisplayName(event.Provider))
	builder.WriteString(" pushed to")
	if branch != "" {
		builder.WriteString(" base branch ")
		builder.WriteString(branch)
	} else {
		builder.WriteString(" a PR base branch")
	}
	if actor := sanitizeServiceEventExternalActor(event.ActorLogin); actor != "" {
		builder.WriteString(" by ")
		builder.WriteString(actor)
	}
	if resource := sanitizeServiceEventExternalDisplayText(event.ResourceIdentifier); resource != "" {
		builder.WriteString(" for ")
		builder.WriteString(resource)
	}
	if url := sanitizeServiceEventExternalDisplayText(event.ResourceURL); url != "" {
		builder.WriteString("\n")
		builder.WriteString(url)
	}
	builder.WriteString("\n\nThis PR may now be out-of-date with the base branch.")
	return builder.String()
}

func sanitizeServiceEventExternalActor(value string) string {
	trimmed := strings.TrimSpace(value)
	trimmed = strings.TrimPrefix(trimmed, "@")
	return strings.Map(func(r rune) rune {
		switch r {
		case '\n', '\r', '\t', '@':
			return -1
		default:
			return r
		}
	}, trimmed)
}

func sanitizeServiceEventExternalDisplayText(value string) string {
	trimmed := strings.Join(strings.Fields(strings.TrimSpace(value)), " ")
	return webhook.TruncateProviderText(escapeServiceEventAgentTeamMentions(trimmed), serviceEventDisplayTextMaxBytes)
}

const serviceEventDisplayTextMaxBytes = 512
const serviceEventBodyExcerptMaxBytes = 4096

func serviceEventExternalBodyBlockquote(value string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return ""
	}
	trimmed = webhook.TruncateProviderText(escapeServiceEventAgentTeamMentions(trimmed), serviceEventBodyExcerptMaxBytes)
	lines := strings.Split(trimmed, "\n")
	if len(lines) > 12 {
		lines = lines[:12]
		lines = append(lines, "…")
	}
	for index, line := range lines {
		lines[index] = "> " + strings.TrimRight(line, "\r")
	}
	return strings.Join(lines, "\n")
}

func escapeServiceEventAgentTeamMentions(value string) string {
	return strings.ReplaceAll(value, "@", "@\u200b")
}

func serviceEventDeliveryCandidateIDs(candidates []serviceEventDeliveryCandidate) []string {
	ids := make([]string, 0, len(candidates))
	for _, candidate := range candidates {
		if id := strings.TrimSpace(candidate.Subscription.ID); id != "" {
			ids = append(ids, id)
		}
	}
	return ids
}

func serviceEventProviderStoredExtra(extra map[string]string) map[string]string {
	if len(extra) == 0 {
		return nil
	}
	stored := make(map[string]string, len(extra))
	for key, value := range extra {
		trimmedKey := strings.TrimSpace(key)
		if trimmedKey == "" {
			continue
		}
		trimmedValue := webhook.TruncateProviderText(strings.TrimSpace(value), serviceEventBodyExcerptMaxBytes)
		if trimmedValue == "" {
			continue
		}
		stored[trimmedKey] = trimmedValue
	}
	if len(stored) == 0 {
		return nil
	}
	return stored
}
