package cmd

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

const (
	envClinicClusterID  = "CLINIC_CLUSTER_ID"
	envClinicRangeStart = "CLINIC_RANGE_START"
	envClinicRangeEnd   = "CLINIC_RANGE_END"

	defaultRangeFallbackWindow       = 5 * time.Minute
	defaultEventsRangeFallbackWindow = 7 * 24 * time.Hour
	resolvedTimeLogLayout            = "2006/01/02 15:04:05.000 -07:00"
)

func requiredFlagOrEnv(flagName, flagValue, envKey string) (string, error) {
	return requiredFlagOrEnvWithContext(nil, flagName, flagValue, envKey)
}

func requiredFlagOrEnvWithContext(ctx *RuntimeContext, flagName, flagValue, envKey string) (string, error) {
	envValue := rawEnvValue(envKey)
	profileValue := profileDefaultForEnvKey(ctx, envKey)
	value, source := resolveValueWithSource(flagValue, envValue, profileValue)
	logFlagValueResolution(ctx, flagName, envKey, value, source, true)
	if value == "" {
		return "", fmt.Errorf("missing required --%s", flagName)
	}
	return value, nil
}

func optionalFlagOrEnvWithContext(ctx *RuntimeContext, flagValue, envKey string) string {
	envValue := rawEnvValue(envKey)
	profileValue := profileDefaultForEnvKey(ctx, envKey)
	value, source := resolveValueWithSource(flagValue, envValue, profileValue)
	logFlagValueResolution(ctx, envKeyToFlagName(envKey), envKey, value, source, false)
	return value
}

func resolveRequiredRangeWithContext(
	ctx *RuntimeContext,
	startFlagName string,
	startFlagValue string,
	endFlagName string,
	endFlagValue string,
) (string, string, error) {
	return resolveRequiredRangeWithFallback(ctx, startFlagName, startFlagValue, endFlagName, endFlagValue, defaultRangeFallbackWindow)
}

func resolveRequiredRangeWithFallback(
	ctx *RuntimeContext,
	startFlagName string,
	startFlagValue string,
	endFlagName string,
	endFlagValue string,
	fallbackWindow time.Duration,
) (string, string, error) {
	resolvedStart := optionalFlagOrEnvWithContext(ctx, startFlagValue, envClinicRangeStart)
	resolvedEnd := optionalFlagOrEnvWithContext(ctx, endFlagValue, envClinicRangeEnd)

	if resolvedStart != "" && resolvedEnd != "" {
		return resolvedStart, resolvedEnd, nil
	}

	if resolvedStart == "" && resolvedEnd == "" {
		fallbackStart, fallbackEnd := latestRangeFallbackWindowUnixSeconds(time.Now(), fallbackWindow)
		label := fallbackWindowLabel(fallbackWindow)
		diagnosticf(ctx, "resolved --%s from default latest %s: %s", startFlagName, label, describeResolvedFlagValue(startFlagName, fallbackStart))
		diagnosticf(ctx, "resolved --%s from default latest %s: %s", endFlagName, label, describeResolvedFlagValue(endFlagName, fallbackEnd))
		return fallbackStart, fallbackEnd, nil
	}

	if resolvedStart == "" {
		logFlagValueResolution(ctx, startFlagName, envClinicRangeStart, "", valueSourceNone, true)
		return "", "", fmt.Errorf("missing required --%s", startFlagName)
	}

	logFlagValueResolution(ctx, endFlagName, envClinicRangeEnd, "", valueSourceNone, true)
	return "", "", fmt.Errorf("missing required --%s", endFlagName)
}

func fallbackWindowLabel(window time.Duration) string {
	switch window {
	case 5 * time.Minute:
		return "5 minutes"
	case 7 * 24 * time.Hour:
		return "7 days"
	default:
		return window.String()
	}
}

func latestRangeFallbackWindowUnixSeconds(now time.Time, window time.Duration) (string, string) {
	endUnix := now.Unix()
	startUnix := now.Add(-window).Unix()
	return strconv.FormatInt(startUnix, 10), strconv.FormatInt(endUnix, 10)
}

func profileDefaultForEnvKey(ctx *RuntimeContext, envKey string) string {
	if ctx == nil || ctx.SelectedProfile == nil {
		return ""
	}

	profile := *ctx.SelectedProfile
	switch envKey {
	case envClinicClusterID:
		return profile.ClusterID
	case envClinicRangeStart:
		return profile.RangeStart
	case envClinicRangeEnd:
		return profile.RangeEnd
	default:
		return ""
	}
}

func rawEnvValue(envKey string) string {
	return os.Getenv(envKey)
}

func logFlagValueResolution(ctx *RuntimeContext, flagName, envKey, value string, source valueSource, required bool) {
	if !diagnosticsEnabled(ctx) {
		return
	}

	switch source {
	case valueSourceFlag:
		diagnosticf(ctx, "resolved --%s from flag --%s: %s", flagName, flagName, describeResolvedFlagValue(flagName, value))
	case valueSourceEnv:
		diagnosticf(ctx, "resolved --%s from env %s: %s", flagName, envKey, describeResolvedFlagValue(flagName, value))
	case valueSourceProfile:
		diagnosticf(ctx, "resolved --%s from selected profile %s: %s", flagName, profileFieldForEnvKey(envKey), describeResolvedFlagValue(flagName, value))
	case valueSourceNone:
		if required {
			diagnosticf(
				ctx,
				"missing --%s; checked flag --%s, env %s, selected profile %s",
				flagName,
				flagName,
				envKey,
				profileFieldForEnvKey(envKey),
			)
		}
	}
}

func describeResolvedFlagValue(flagName, value string) string {
	base := fmt.Sprintf("%q", value)
	human := resolvedTimeForDiagnostics(flagName, value)
	if human == "" {
		return base
	}
	return fmt.Sprintf("%s (%s)", base, human)
}

func resolvedTimeForDiagnostics(flagName, value string) string {
	if flagName != "start" && flagName != "end" {
		return ""
	}

	parsedUnixNano, err := parseFlexibleTimeToUnixNano(value, time.Local)
	if err != nil {
		return ""
	}

	return time.Unix(0, parsedUnixNano).In(time.Local).Format(resolvedTimeLogLayout)
}

func profileFieldForEnvKey(envKey string) string {
	switch envKey {
	case envClinicClusterID:
		return "cluster_id"
	case envClinicRangeStart:
		return "range_start"
	case envClinicRangeEnd:
		return "range_end"
	default:
		return "unknown"
	}
}

func envKeyToFlagName(envKey string) string {
	switch envKey {
	case envClinicClusterID:
		return "cluster-id"
	case envClinicRangeStart:
		return "start"
	case envClinicRangeEnd:
		return "end"
	default:
		return envKey
	}
}
