package services

import (
	"strings"

	dto "astrology-api/dto/astrology"
)

//////////////////////////////////////////////////////////////
// Horoscope Client Detection
//////////////////////////////////////////////////////////////
//
// platform / device_type / app_version for the horoscope_requests log, shared
// by the customer and astrologer controllers. Each value is taken from the
// first place that has it:
//
//   1. the X-Platform / X-Device-Type / X-App-Version headers
//   2. the same values in the JSON body (platform, device_type, app_version),
//      for clients that cannot easily set headers
//   3. device_type only: guessed from the User-Agent (Android, iOS, browser)
//
// app_version has no fallback: only the client knows it, so it stays NULL
// when neither the header nor the body carries it.

// HeaderFunc reads one request header, e.g. gin's ctx.GetHeader.
type HeaderFunc func(name string) string

// ResolveHoroscopeClient fills the client fields of caller. allowed lists the
// platforms this side may claim; the first entry is the default.
func ResolveHoroscopeClient(
	caller *dto.HoroscopeCaller,
	header HeaderFunc,
	body dto.ClientInfo,
	allowed []string,
) {

	caller.Platform, caller.DeviceType, caller.AppVersion = ResolveClient(header, body, allowed)
}

// ResolveClient returns platform, device_type and app_version for any request,
// by the rules above. The astrology usage log uses it too, so both reports
// agree on where a call came from.
func ResolveClient(
	header HeaderFunc,
	body dto.ClientInfo,
	allowed []string,
) (string, string, string) {

	caller := &dto.HoroscopeCaller{}

	declared := firstNonBlank(header("X-Platform"), header("Platform"), body.Platform)

	caller.Platform = resolveHoroscopePlatform(declared, allowed)

	caller.DeviceType = normalizeHoroscopeDevice(
		firstNonBlank(header("X-Device-Type"), header("Device-Type"), body.DeviceType),
	)

	// Some clients send the OS as the platform ("android", "ios").
	if caller.DeviceType == "" {
		caller.DeviceType = normalizeHoroscopeDevice(declared)
	}

	if caller.DeviceType == "" {
		caller.DeviceType = deviceFromUserAgent(header("User-Agent"))
	}

	// A browser that did not name its platform is the web side (allowed[1]),
	// not the app it would otherwise default to.
	if declared == "" && caller.DeviceType == "web" && len(allowed) > 1 {
		caller.Platform = allowed[1]
	}

	// A web platform is a browser, whatever the User-Agent guessed.
	if caller.Platform == HoroscopePlatformWeb || caller.Platform == HoroscopePlatformAstrologerWeb {
		caller.DeviceType = "web"
	}

	version := firstNonBlank(header("X-App-Version"), header("App-Version"), body.AppVersion)

	if len(version) > 20 {
		version = version[:20]
	}

	caller.AppVersion = version

	return caller.Platform, caller.DeviceType, caller.AppVersion
}

func firstNonBlank(values ...string) string {

	for _, value := range values {

		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}

	return ""
}

func resolveHoroscopePlatform(value string, allowed []string) string {

	value = strings.ToLower(value)

	for _, platform := range allowed {

		if value == platform {
			return platform
		}
	}

	return allowed[0]
}

func normalizeHoroscopeDevice(value string) string {

	switch strings.ToLower(value) {
	case "android":
		return "android"
	case "ios", "iphone", "ipad":
		return "ios"
	case "web", "browser":
		return "web"
	}

	return ""
}

// deviceFromUserAgent recognises the three device types from common agents:
// Android WebViews / okhttp / Dart, iOS (CFNetwork, iPhone), and browsers.
// Anything else, such as Postman or curl, is left unknown.
func deviceFromUserAgent(agent string) string {

	agent = strings.ToLower(agent)

	switch {
	case agent == "":
		return ""
	case strings.Contains(agent, "android"), strings.Contains(agent, "okhttp"):
		return "android"
	case strings.Contains(agent, "iphone"), strings.Contains(agent, "ipad"),
		strings.Contains(agent, "ios"), strings.Contains(agent, "cfnetwork"),
		strings.Contains(agent, "darwin"):
		return "ios"
	case strings.Contains(agent, "mozilla"):
		return "web"
	}

	return ""
}
