package cpnotice

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// cpApplication mirrors the user-service CP outbox payload without importing user-service internals.
type cpApplication struct {
	ApplicationID string
	RelationType  string
	Status        string
	Requester     cpUser
	Target        cpUser
	RoomID        string
	RoomRegionID  int64
	RoomLocked    bool
	Gift          cpGift
	ExpiresAtMS   int64
	DecidedAtMS   int64
}

type cpUser struct {
	UserID        int64
	DisplayUserID string
	Username      string
	Avatar        string
}

type cpGift struct {
	GiftID           string
	GiftName         string
	GiftIconURL      string
	GiftAnimationURL string
	GiftCount        int64
	GiftValue        int64
	BillingReceiptID string
}

func decodeObject(value string) (map[string]any, error) {
	var payload map[string]any
	if err := json.Unmarshal([]byte(strings.TrimSpace(value)), &payload); err != nil {
		return nil, fmt.Errorf("invalid cp payload json: %w", err)
	}
	if payload == nil {
		payload = map[string]any{}
	}
	return payload, nil
}

func applicationFromObject(obj map[string]any) (cpApplication, error) {
	if len(obj) == 0 {
		return cpApplication{}, fmt.Errorf("cp application payload is missing")
	}
	app := cpApplication{
		ApplicationID: stringValue(obj, "application_id", "ApplicationID"),
		RelationType:  stringValue(obj, "relation_type", "RelationType"),
		Status:        stringValue(obj, "status", "Status"),
		Requester:     userFromObject(firstObject(obj, "requester", "Requester")),
		Target:        userFromObject(firstObject(obj, "target", "Target")),
		RoomID:        stringValue(obj, "room_id", "RoomID"),
		RoomRegionID:  int64Value(obj, "room_region_id", "RoomRegionID"),
		RoomLocked:    boolValue(obj, "room_locked", "RoomLocked"),
		Gift:          giftFromObject(firstObject(obj, "gift", "Gift")),
		ExpiresAtMS:   int64Value(obj, "expires_at_ms", "ExpiresAtMS"),
		DecidedAtMS:   int64Value(obj, "decided_at_ms", "DecidedAtMS"),
	}
	if app.ApplicationID == "" || app.RelationType == "" || app.Requester.UserID <= 0 || app.Target.UserID <= 0 {
		return cpApplication{}, fmt.Errorf("cp application payload is incomplete")
	}
	return app, nil
}

func userFromObject(obj map[string]any) cpUser {
	return cpUser{
		UserID:        int64Value(obj, "user_id", "UserID"),
		DisplayUserID: stringValue(obj, "display_user_id", "DisplayUserID"),
		Username:      stringValue(obj, "username", "Username"),
		Avatar:        stringValue(obj, "avatar", "Avatar"),
	}
}

func giftFromObject(obj map[string]any) cpGift {
	return cpGift{
		GiftID:           stringValue(obj, "gift_id", "GiftID"),
		GiftName:         stringValue(obj, "gift_name", "GiftName"),
		GiftIconURL:      stringValue(obj, "gift_icon_url", "GiftIconURL"),
		GiftAnimationURL: stringValue(obj, "gift_animation_url", "GiftAnimationURL"),
		GiftCount:        int64Value(obj, "gift_count", "GiftCount"),
		GiftValue:        int64Value(obj, "gift_value", "GiftValue"),
		BillingReceiptID: stringValue(obj, "billing_receipt_id", "BillingReceiptID"),
	}
}

func firstObject(obj map[string]any, keys ...string) map[string]any {
	for _, key := range keys {
		if value, ok := obj[key]; ok {
			if typed, ok := value.(map[string]any); ok {
				return typed
			}
			// json.Unmarshal into map can leave nested structs only as maps; this defensive re-marshal keeps tests and
			// future typed adapters from breaking the parser if they pass a struct-like object.
			body, err := json.Marshal(value)
			if err == nil {
				var nested map[string]any
				if json.Unmarshal(body, &nested) == nil {
					return nested
				}
			}
		}
	}
	return map[string]any{}
}

func stringValue(obj map[string]any, keys ...string) string {
	for _, key := range keys {
		switch value := obj[key].(type) {
		case string:
			return strings.TrimSpace(value)
		case json.Number:
			return value.String()
		case float64:
			if value == float64(int64(value)) {
				return strconv.FormatInt(int64(value), 10)
			}
		case int64:
			return strconv.FormatInt(value, 10)
		case int:
			return strconv.Itoa(value)
		}
	}
	return ""
}

func int64Value(obj map[string]any, keys ...string) int64 {
	for _, key := range keys {
		switch value := obj[key].(type) {
		case float64:
			return int64(value)
		case int64:
			return value
		case int:
			return int64(value)
		case json.Number:
			parsed, _ := value.Int64()
			return parsed
		case string:
			parsed, _ := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
			return parsed
		}
	}
	return 0
}

func boolValue(obj map[string]any, keys ...string) bool {
	for _, key := range keys {
		switch value := obj[key].(type) {
		case bool:
			return value
		case float64:
			return value != 0
		case int64:
			return value != 0
		case int:
			return value != 0
		case string:
			parsed, _ := strconv.ParseBool(strings.TrimSpace(value))
			return parsed
		}
	}
	return false
}
