package rest

import (
	"encoding/json"
	"net/http"
	"net/url"
	"strings"

	"github.com/pingcap/agent-git-service/internal/metrics"
	"github.com/pingcap/agent-git-service/internal/rest/respond"
	"github.com/pingcap/agent-git-service/internal/tenant"
)

const (
	maxAnalyticsRequestBytes = 64 << 10
	maxAnalyticsEvents       = 20
)

type analyticsEventPayload struct {
	EventName      string `json:"event_name"`
	Name           string `json:"name"`
	Source         string `json:"source"`
	Path           string `json:"path"`
	TargetPath     string `json:"target_path"`
	Page           string `json:"page"`
	Platform       string `json:"platform"`
	Location       string `json:"location"`
	ButtonID       string `json:"button_id"`
	Success        *bool  `json:"success"`
	TrafficSource  string `json:"traffic_source"`
	UTMSource      string `json:"utm_source"`
	ReferrerDomain string `json:"referrer_domain"`
}

type analyticsEventsRequest struct {
	Events []analyticsEventPayload `json:"events"`

	EventName      string `json:"event_name"`
	Name           string `json:"name"`
	Source         string `json:"source"`
	Path           string `json:"path"`
	TargetPath     string `json:"target_path"`
	Page           string `json:"page"`
	Platform       string `json:"platform"`
	Location       string `json:"location"`
	ButtonID       string `json:"button_id"`
	Success        *bool  `json:"success"`
	TrafficSource  string `json:"traffic_source"`
	UTMSource      string `json:"utm_source"`
	ReferrerDomain string `json:"referrer_domain"`
}

type normalizedAnalyticsEvent struct {
	Event         string
	Source        string
	Page          string
	Platform      string
	Location      string
	Success       string
	TrafficSource string
}

// CreateAnalyticsEvents handles public frontend analytics events.
func (d *Deps) CreateAnalyticsEvents(w http.ResponseWriter, r *http.Request) {
	r.Body = http.MaxBytesReader(w, r.Body, maxAnalyticsRequestBytes)

	var req analyticsEventsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respond.Error(w, http.StatusBadRequest, "invalid analytics event payload")
		return
	}

	events := req.Events
	if len(events) == 0 {
		events = []analyticsEventPayload{req.singleEvent()}
	}
	if len(events) == 0 || len(events) > maxAnalyticsEvents {
		respond.ValidationFailed(w, "events must contain between 1 and 20 items")
		return
	}

	accepted := 0
	info, _ := tenant.FromContext(r.Context())
	for _, event := range events {
		normalized, ok := normalizeAnalyticsEvent(event)
		if !ok {
			continue
		}
		metrics.IncFrontendEvent(
			string(info.ID),
			normalized.Event,
			normalized.Source,
			normalized.Page,
			normalized.Platform,
			normalized.Location,
			normalized.Success,
			normalized.TrafficSource,
		)
		accepted++
	}
	if accepted == 0 {
		respond.ValidationFailed(w, "at least one event_name is required")
		return
	}

	respond.JSON(w, http.StatusAccepted, map[string]any{"accepted": accepted})
}

func (r analyticsEventsRequest) singleEvent() analyticsEventPayload {
	return analyticsEventPayload{
		EventName:      r.EventName,
		Name:           r.Name,
		Source:         r.Source,
		Path:           r.Path,
		TargetPath:     r.TargetPath,
		Page:           r.Page,
		Platform:       r.Platform,
		Location:       r.Location,
		ButtonID:       r.ButtonID,
		Success:        r.Success,
		TrafficSource:  r.TrafficSource,
		UTMSource:      r.UTMSource,
		ReferrerDomain: r.ReferrerDomain,
	}
}

func normalizeAnalyticsEvent(event analyticsEventPayload) (normalizedAnalyticsEvent, bool) {
	name := normalizeAnalyticsEventName(firstNonEmpty(event.EventName, event.Name))
	if name == "" {
		return normalizedAnalyticsEvent{}, false
	}

	pagePath := firstNonEmpty(event.TargetPath, event.Path)
	if name != "blog_click" {
		pagePath = event.Path
	}

	return normalizedAnalyticsEvent{
		Event:         name,
		Source:        normalizeAnalyticsSource(event.Source, event.Path),
		Page:          normalizeAnalyticsPage(firstNonEmpty(event.Page, pagePath)),
		Platform:      normalizeAnalyticsPlatform(event.Platform, event.ButtonID),
		Location:      normalizeAnalyticsLocation(event.Location, event.ButtonID),
		Success:       normalizeAnalyticsSuccess(event.Success),
		TrafficSource: normalizeAnalyticsTrafficSource(event.TrafficSource, event.UTMSource, event.ReferrerDomain),
	}, true
}

func normalizeAnalyticsEventName(raw string) string {
	token := labelToken(strings.ReplaceAll(raw, "-", "_"))
	switch token {
	case "":
		return ""
	case "page_view":
		return "page_view"
	case "install_copy", "install_command_copied":
		return "install_copy"
	case "console_click", "console_clicked", "cta_console_click":
		return "console_click"
	case "blog_click":
		return "blog_click"
	case "nav_click", "nav_clicked":
		return "nav_click"
	case "footer_click", "footer_clicked":
		return "footer_click"
	case "section_view", "section_viewed":
		return "section_view"
	case "scroll_depth":
		return "scroll_depth"
	case "language_change", "language_changed":
		return "language_change"
	case "scenario_tab_click", "scenario_tab_clicked":
		return "scenario_tab_click"
	case "theme_toggle", "theme_toggled":
		return "theme_toggle"
	case "frontend_error":
		return "frontend_error"
	default:
		return "other"
	}
}

func normalizeAnalyticsSource(source, path string) string {
	switch labelToken(source) {
	case "landing_page", "blog", "docs", "console":
		return labelToken(source)
	}
	cleanPath := cleanAnalyticsPath(path)
	switch {
	case strings.HasPrefix(cleanPath, "/blog"):
		return "blog"
	case strings.HasPrefix(cleanPath, "/docs"):
		return "docs"
	default:
		return "landing_page"
	}
}

func normalizeAnalyticsPage(raw string) string {
	path := cleanAnalyticsPath(raw)
	switch {
	case path == "", path == "/", path == "/index.html":
		return "home"
	case path == "/SKILL.md":
		return "skill"
	case path == "/404":
		return "not_found"
	case path == "/blog", path == "/blog/":
		return "blog_index"
	case strings.HasPrefix(path, "/blog/"):
		slug := strings.Trim(strings.TrimPrefix(path, "/blog/"), "/")
		if slug == "" {
			return "blog_index"
		}
		return "blog_" + labelToken(slug)
	case path == "/docs", path == "/docs/":
		return "docs_index"
	case strings.HasPrefix(path, "/docs/"):
		section := strings.Trim(strings.TrimPrefix(path, "/docs/"), "/")
		if section == "" {
			return "docs_index"
		}
		parts := strings.Split(section, "/")
		if len(parts) > 2 {
			parts = parts[:2]
		}
		return "docs_" + labelToken(strings.Join(parts, "_"))
	default:
		return "other"
	}
}

func normalizeAnalyticsPlatform(platform, buttonID string) string {
	raw := strings.ToLower(firstNonEmpty(platform, buttonID))
	switch {
	case strings.Contains(raw, "claude"):
		return "claude_code"
	case strings.Contains(raw, "codex"):
		return "codex"
	case strings.Contains(raw, "hermes"):
		return "hermes"
	case strings.Contains(raw, "openclaw"):
		return "openclaw"
	case strings.Contains(raw, "mcp"):
		return "mcp"
	default:
		return "unknown"
	}
}

func normalizeAnalyticsLocation(location, buttonID string) string {
	raw := labelToken(firstNonEmpty(location, buttonID))
	switch {
	case raw == "hero" || strings.HasPrefix(raw, "hero_"):
		return "hero"
	case raw == "cta" || strings.HasPrefix(raw, "cta_"):
		return "cta"
	case raw == "nav" || strings.HasPrefix(raw, "nav_"):
		return "nav"
	case raw == "footer" || strings.HasPrefix(raw, "footer_"):
		return "footer"
	case raw == "blog" || strings.HasPrefix(raw, "blog_"):
		return "blog"
	case raw == "article" || strings.HasPrefix(raw, "article_"):
		return "article"
	case raw == "docs" || strings.HasPrefix(raw, "docs_"):
		return "docs"
	default:
		return "unknown"
	}
}

func normalizeAnalyticsSuccess(success *bool) string {
	if success == nil {
		return "unknown"
	}
	if *success {
		return "true"
	}
	return "false"
}

func normalizeAnalyticsTrafficSource(trafficSource, utmSource, referrerDomain string) string {
	source := strings.ToLower(strings.TrimSpace(firstNonEmpty(trafficSource, utmSource)))
	switch {
	case source == "x":
		return "twitter"
	case strings.Contains(source, "twitter"):
		return "twitter"
	case strings.Contains(source, "discord"):
		return "discord"
	case strings.Contains(source, "github"):
		return "github"
	case strings.Contains(source, "google"):
		return "organic"
	case strings.Contains(source, "newsletter"):
		return "newsletter"
	case strings.Contains(source, "blog"):
		return "blog"
	case source == "direct":
		return "direct"
	case source != "":
		return "other"
	}

	referrer := strings.ToLower(strings.TrimSpace(referrerDomain))
	switch {
	case referrer == "":
		return "direct"
	case strings.Contains(referrer, "clawmem.ai"):
		return "internal"
	case strings.Contains(referrer, "google.") || strings.Contains(referrer, "bing.") || strings.Contains(referrer, "duckduckgo."):
		return "organic"
	case strings.Contains(referrer, "twitter.") || strings.Contains(referrer, "x.com"):
		return "twitter"
	case strings.Contains(referrer, "github."):
		return "github"
	case strings.Contains(referrer, "discord."):
		return "discord"
	default:
		return "referral"
	}
}

func cleanAnalyticsPath(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ""
	}
	if parsed, err := url.Parse(raw); err == nil && parsed.Path != "" {
		raw = parsed.Path
	}
	if !strings.HasPrefix(raw, "/") {
		raw = "/" + raw
	}
	return strings.TrimSpace(raw)
}

func labelToken(raw string) string {
	raw = strings.ToLower(strings.TrimSpace(raw))
	if raw == "" {
		return ""
	}
	var b strings.Builder
	lastUnderscore := false
	for _, r := range raw {
		switch {
		case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
			b.WriteRune(r)
			lastUnderscore = false
		case r == '_', r == '-':
			if !lastUnderscore {
				b.WriteByte('_')
				lastUnderscore = true
			}
		default:
			if !lastUnderscore {
				b.WriteByte('_')
				lastUnderscore = true
			}
		}
		if b.Len() >= 80 {
			break
		}
	}
	return strings.Trim(b.String(), "_")
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
