package utils

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

// OAuthConsumer represents OAuth consumer credentials
type OAuthConsumer struct {
	ConsumerKey    string `json:"consumer_key"`
	ConsumerSecret string `json:"consumer_secret"`
}

var oauthConsumer *OAuthConsumer

// LoadOAuthConsumer loads OAuth consumer credentials
func LoadOAuthConsumer() (*OAuthConsumer, error) {
	if oauthConsumer != nil {
		return oauthConsumer, nil
	}

	// First try to get from S3 (like the Python library)
	resp, err := http.Get("https://thegarth.s3.amazonaws.com/oauth_consumer.json")
	if err == nil {
		defer resp.Body.Close()
		if resp.StatusCode == 200 {
			var consumer OAuthConsumer
			if err := json.NewDecoder(resp.Body).Decode(&consumer); err == nil {
				oauthConsumer = &consumer
				return oauthConsumer, nil
			}
		}
	}

	// Fallback to hardcoded values
	oauthConsumer = &OAuthConsumer{
		ConsumerKey:    "fc320c35-fbdc-4308-b5c6-8e41a8b2e0c8",
		ConsumerSecret: "8b344b8c-5bd5-4b7b-9c98-ad76a6bbf0e7",
	}
	return oauthConsumer, nil
}

// GenerateNonce generates a random nonce for OAuth
func GenerateNonce() string {
	b := make([]byte, 32)
	rand.Read(b)
	return base64.StdEncoding.EncodeToString(b)
}

// GenerateTimestamp generates a timestamp for OAuth
func GenerateTimestamp() string {
	return strconv.FormatInt(time.Now().Unix(), 10)
}

// PercentEncode URL encodes a string
func PercentEncode(s string) string {
	return url.QueryEscape(s)
}

// CreateSignatureBaseString creates the base string for OAuth signing
func CreateSignatureBaseString(method, baseURL string, params map[string]string) string {
	var keys []string
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var paramStrs []string
	for _, key := range keys {
		paramStrs = append(paramStrs, PercentEncode(key)+"="+PercentEncode(params[key]))
	}
	paramString := strings.Join(paramStrs, "&")

	return method + "&" + PercentEncode(baseURL) + "&" + PercentEncode(paramString)
}

// CreateSigningKey creates the signing key for OAuth
func CreateSigningKey(consumerSecret, tokenSecret string) string {
	return PercentEncode(consumerSecret) + "&" + PercentEncode(tokenSecret)
}

// SignRequest signs an OAuth request
func SignRequest(consumerSecret, tokenSecret, baseString string) string {
	signingKey := CreateSigningKey(consumerSecret, tokenSecret)
	mac := hmac.New(sha1.New, []byte(signingKey))
	mac.Write([]byte(baseString))
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// CreateOAuth1AuthorizationHeader creates the OAuth1 authorization header
func CreateOAuth1AuthorizationHeader(method, requestURL string, params map[string]string, consumerKey, consumerSecret, token, tokenSecret string) string {
	oauthParams := map[string]string{
		"oauth_consumer_key":     consumerKey,
		"oauth_nonce":            GenerateNonce(),
		"oauth_signature_method": "HMAC-SHA1",
		"oauth_timestamp":        GenerateTimestamp(),
		"oauth_version":          "1.0",
	}

	if token != "" {
		oauthParams["oauth_token"] = token
	}

	// Combine OAuth params with request params
	allParams := make(map[string]string)
	for k, v := range oauthParams {
		allParams[k] = v
	}
	for k, v := range params {
		allParams[k] = v
	}

	// Parse URL to get base URL without query params
	parsedURL, _ := url.Parse(requestURL)
	baseURL := parsedURL.Scheme + "://" + parsedURL.Host + parsedURL.Path

	// Create signature base string
	baseString := CreateSignatureBaseString(method, baseURL, allParams)

	// Sign the request
	signature := SignRequest(consumerSecret, tokenSecret, baseString)
	oauthParams["oauth_signature"] = signature

	// Build authorization header
	var headerParts []string
	for key, value := range oauthParams {
		headerParts = append(headerParts, PercentEncode(key)+"=\""+PercentEncode(value)+"\"")
	}
	sort.Strings(headerParts)

	return "OAuth " + strings.Join(headerParts, ", ")
}

// Min returns the smaller of two integers
func Min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// DateRange generates a date range from end date backwards for n days
func DateRange(end time.Time, days int) []time.Time {
	dates := make([]time.Time, days)
	for i := 0; i < days; i++ {
		dates[i] = end.AddDate(0, 0, -i)
	}
	return dates
}

// CamelToSnake converts a camelCase string to snake_case
func CamelToSnake(s string) string {
	matchFirstCap := regexp.MustCompile("(.)([A-Z][a-z]+)")
	matchAllCap := regexp.MustCompile("([a-z0-9])([A-Z])")

	snake := matchFirstCap.ReplaceAllString(s, "${1}_${2}")
	snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
	return strings.ToLower(snake)
}

// CamelToSnakeDict recursively converts map keys from camelCase to snake_case
func CamelToSnakeDict(m map[string]interface{}) map[string]interface{} {
	snakeDict := make(map[string]interface{})
	for k, v := range m {
		snakeKey := CamelToSnake(k)
		// Handle nested maps
		if nestedMap, ok := v.(map[string]interface{}); ok {
			snakeDict[snakeKey] = CamelToSnakeDict(nestedMap)
		} else if nestedSlice, ok := v.([]interface{}); ok {
			// Handle slices of maps
			var newSlice []interface{}
			for _, item := range nestedSlice {
				if itemMap, ok := item.(map[string]interface{}); ok {
					newSlice = append(newSlice, CamelToSnakeDict(itemMap))
				} else {
					newSlice = append(newSlice, item)
				}
			}
			snakeDict[snakeKey] = newSlice
		} else {
			snakeDict[snakeKey] = v
		}
	}
	return snakeDict
}

// FormatEndDate converts various date formats to time.Time
func FormatEndDate(end interface{}) time.Time {
	if end == nil {
		return time.Now().UTC().Truncate(24 * time.Hour)
	}

	switch v := end.(type) {
	case string:
		t, _ := time.Parse("2006-01-02", v)
		return t
	case time.Time:
		return v
	default:
		return time.Now().UTC().Truncate(24 * time.Hour)
	}
}

// GetLocalizedDateTime converts GMT and local timestamps to localized time
func GetLocalizedDateTime(gmtTimestamp, localTimestamp int64) time.Time {
	localDiff := localTimestamp - gmtTimestamp
	offset := time.Duration(localDiff) * time.Millisecond
	loc := time.FixedZone("", int(offset.Seconds()))
	gmtTime := time.Unix(0, gmtTimestamp*int64(time.Millisecond)).UTC()
	return gmtTime.In(loc)
}
