package commerce

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"
)

func (s *Service) AdminCloudResources(ctx context.Context, cloudServerID int64, kind string) ([]map[string]any, error) {
	server, err := s.adminCloudServer(ctx, cloudServerID)
	if err != nil {
		return nil, err
	}
	if server.Disabled {
		return nil, BadRequest("cloud server is disabled")
	}
	if server.ServerType != "dcim_cloud" {
		return []map[string]any{}, nil
	}
	if server.AccountType == "agent" {
		return []map[string]any{}, nil
	}
	path := ""
	payload := map[string]any{}
	switch strings.ToLower(strings.TrimSpace(kind)) {
	case "image", "images":
		path = "/image"
		payload = map[string]any{"per_page": "9999", "sort": "asc"}
	case "area", "areas":
		path = "/areas"
		payload = map[string]any{"sort": "asc", "list_type": "all"}
	default:
		return nil, BadRequest("unknown cloud resource kind: " + kind)
	}
	result, err := CallDCIMPath(ctx, server, path, "GET", payload, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 {
		return nil, BadRequest(fmt.Sprintf("upstream HTTP %d: %s", result.StatusCode, trimText(result.Text, 200)))
	}
	rows := upstreamListRows(result.Body)
	out := make([]map[string]any, 0, len(rows))
	for _, row := range rows {
		if strings.HasPrefix(strings.ToLower(kind), "image") {
			if status, ok := int64FromAny(row["status"]); ok && status != 1 {
				continue
			}
			groupName := firstString(row["group_name"], nestedMapString(row, "group", "name"))
			label := strings.Trim(strings.TrimSpace(groupName+" / "+firstString(row["name"])), " /")
			if label == "" {
				label = firstString(row["id"])
			}
			out = append(out, map[string]any{"id": fmt.Sprint(row["id"]), "label": label, "extra": row["filename"], "raw": row})
			continue
		}
		out = append(out, map[string]any{"id": fmt.Sprint(row["id"]), "label": firstString(row["name"], row["id"]), "extra": row["country_code"], "raw": row})
	}
	return out, nil
}

func (s *Service) AdminZJMFFinanceProducts(ctx context.Context, cloudServerID int64) (map[string]any, error) {
	server, err := s.adminZJMFFinanceServer(ctx, cloudServerID)
	if err != nil {
		return nil, err
	}
	path := "/cart/all"
	if server.AccountType == "resource" {
		path = "/resource/products"
	}
	result, err := CallZJMFAPI(ctx, server, path, "GET", map[string]any{}, 30*time.Second)
	if err != nil {
		return nil, BadGateway(err.Error())
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		return nil, BadGateway("zjmf product list failed: " + upstreamMessage(result.Body, result.Text))
	}
	data, _ := result.Body["data"].(map[string]any)
	return normalizeZJMFProductList(data), nil
}

func (s *Service) AdminZJMFFinanceProductConfig(ctx context.Context, cloudServerID int64, pid string) (map[string]any, error) {
	server, err := s.adminZJMFFinanceServer(ctx, cloudServerID)
	if err != nil {
		return nil, err
	}
	result, err := CallZJMFAPI(ctx, server, "/cart/get_product_config", "GET", map[string]any{"pid": pid}, 30*time.Second)
	if err != nil {
		return nil, BadGateway(err.Error())
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		return nil, BadGateway("zjmf product config failed: " + upstreamMessage(result.Body, result.Text))
	}
	data, _ := result.Body["data"].(map[string]any)
	return normalizeZJMFProductConfig(data), nil
}

func (s *Service) adminCloudServer(ctx context.Context, id int64) (CloudServer, error) {
	var server CloudServer
	if err := s.db.WithContext(ctx).Where("id = ?", id).First(&server).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return server, NotFound("cloud server not found")
		}
		return server, err
	}
	return server, nil
}

func (s *Service) adminZJMFFinanceServer(ctx context.Context, id int64) (CloudServer, error) {
	server, err := s.adminCloudServer(ctx, id)
	if err != nil {
		return server, err
	}
	if server.Disabled {
		return server, BadRequest("cloud server is disabled")
	}
	if server.ServerType != "zjmf_finance" {
		return server, BadRequest("please select a ZJMF finance cloud server")
	}
	return server, nil
}

func upstreamListRows(body map[string]any) []map[string]any {
	data := body["data"]
	if m, ok := data.(map[string]any); ok {
		for _, key := range []string{"data", "list", "items", "rows"} {
			if rows := anyListMaps(m[key]); len(rows) > 0 {
				return rows
			}
		}
	}
	if rows := anyListMaps(data); len(rows) > 0 {
		return rows
	}
	return anyListMaps(body["list"])
}

func anyListMaps(value any) []map[string]any {
	list, ok := value.([]any)
	if !ok {
		return nil
	}
	out := make([]map[string]any, 0, len(list))
	for _, item := range list {
		if row, ok := item.(map[string]any); ok {
			out = append(out, row)
		}
	}
	return out
}

func nestedMapString(row map[string]any, keys ...string) string {
	var current any = row
	for _, key := range keys {
		m, ok := current.(map[string]any)
		if !ok {
			return ""
		}
		current = m[key]
	}
	return firstString(current)
}

func normalizeZJMFProductList(data map[string]any) map[string]any {
	if data == nil {
		data = map[string]any{}
	}
	source := anyListMaps(data["products"])
	if len(source) == 0 {
		source = anyListMaps(data["list"])
	}
	if len(source) == 0 {
		source = anyListMaps(data["data"])
	}
	groups := []map[string]any{}
	products := []map[string]any{}
	for _, row := range source {
		groupProducts := anyListMaps(row["products"])
		if len(groupProducts) > 0 {
			normalized := []map[string]any{}
			for _, product := range groupProducts {
				if item := normalizeZJMFProduct(product, row); item != nil {
					normalized = append(normalized, item)
					products = append(products, item)
				}
			}
			groups = append(groups, map[string]any{"id": stringOrNil(row["id"]), "name": firstString(row["name"], row["group_name"], "Ungrouped"), "products": normalized})
			continue
		}
		if item := normalizeZJMFProduct(row, nil); item != nil {
			products = append(products, item)
		}
	}
	count := len(products)
	if parsed, ok := int64FromAny(data["count"]); ok {
		count = int(parsed)
	}
	return map[string]any{"count": count, "currency": data["currency"], "groups": groups, "products": products}
}

func normalizeZJMFProduct(product map[string]any, group map[string]any) map[string]any {
	rawID := firstString(product["id"], product["pid"], product["product_id"])
	if rawID == "" {
		return nil
	}
	groupID := stringOrNil(product["gid"])
	groupName := firstString(product["group_name"])
	if group != nil {
		groupID = stringOrNil(group["id"])
		groupName = firstString(group["name"], group["group_name"])
	}
	return map[string]any{
		"id":          rawID,
		"name":        firstString(product["name"], "Product "+rawID),
		"type":        product["type"],
		"description": product["description"],
		"group_id":    groupID,
		"group_name":  nilIfEmpty(groupName),
	}
}

func normalizeZJMFProductConfig(data map[string]any) map[string]any {
	if data == nil {
		data = map[string]any{}
	}
	product, _ := data["products"].(map[string]any)
	if product == nil {
		product, _ = data["product"].(map[string]any)
	}
	customFields := anyListMaps(data["customfields"])
	pricingRows := anyListMaps(data["product_pricings"])
	configGroupsRaw := anyListMaps(data["config_groups"])
	configGroups := make([]map[string]any, 0, len(configGroupsRaw))
	optionCount := 0
	subOptionCount := 0
	for _, group := range configGroupsRaw {
		summary := summarizeZJMFConfigGroup(group)
		if options, ok := summary["options"].([]map[string]any); ok {
			optionCount += len(options)
			for _, option := range options {
				if n, ok := option["sub_count"].(int); ok {
					subOptionCount += n
				}
			}
		}
		configGroups = append(configGroups, summary)
	}
	currency := any(nil)
	if len(pricingRows) > 0 {
		currency = pricingRows[0]["code"]
	}
	return map[string]any{
		"product": map[string]any{
			"id":            stringOrNil(product["id"]),
			"name":          product["name"],
			"type":          product["type"],
			"description":   product["description"],
			"pay_method":    product["pay_method"],
			"stock_control": product["stock_control"],
			"qty":           product["qty"],
			"hidden":        product["hidden"],
			"retired":       product["retired"],
		},
		"currency":            currency,
		"cycles":              extractZJMFPriceCycles(pricingRows),
		"customfields":        summarizeZJMFCustomFields(customFields),
		"customfield_count":   len(customFields),
		"config_groups":       configGroups,
		"config_group_count":  len(configGroups),
		"config_option_count": optionCount,
		"sub_option_count":    subOptionCount,
	}
}

func summarizeZJMFCustomFields(rows []map[string]any) []map[string]any {
	out := make([]map[string]any, 0, len(rows))
	for _, item := range rows {
		out = append(out, map[string]any{"id": stringOrNil(item["id"]), "fieldname": item["fieldname"], "fieldtype": item["fieldtype"], "required": truthyAny(item["required"]), "description": item["description"], "fieldoptions": item["fieldoptions"]})
	}
	return out
}

func summarizeZJMFConfigGroup(group map[string]any) map[string]any {
	optionsRaw := anyListMaps(group["options"])
	options := make([]map[string]any, 0, len(optionsRaw))
	for _, option := range optionsRaw {
		options = append(options, summarizeZJMFConfigOption(option))
	}
	return map[string]any{"id": stringOrNil(group["id"]), "name": group["name"], "description": group["description"], "options": options}
}

func summarizeZJMFConfigOption(option map[string]any) map[string]any {
	subsRaw := anyListMaps(option["sub"])
	subs := make([]map[string]any, 0, minInt(len(subsRaw), 20))
	for i, sub := range subsRaw {
		if i >= 20 {
			break
		}
		subs = append(subs, map[string]any{"id": stringOrNil(sub["id"]), "option_name": sub["option_name"], "qty_minimum": sub["qty_minimum"], "qty_maximum": sub["qty_maximum"]})
	}
	return map[string]any{"id": stringOrNil(option["id"]), "option_name": firstString(option["option_name"], option["name"]), "option_type": option["option_type"], "sub_count": len(subsRaw), "subs": subs}
}

func extractZJMFPriceCycles(rows []map[string]any) []map[string]any {
	type cycleDef struct {
		key      string
		label    string
		price    string
		setupFee string
	}
	defs := []cycleDef{
		{"monthly", "Monthly", "monthly", "msetupfee"},
		{"quarterly", "Quarterly", "quarterly", "qsetupfee"},
		{"semi_annually", "Semi-annually", "semiannually", "ssetupfee"},
		{"annually", "Annually", "annually", "asetupfee"},
		{"biennially", "Biennially", "biennially", "bsetupfee"},
		{"triennially", "Triennially", "triennially", "tsetupfee"},
		{"one_time", "One-time", "onetime", "osetupfee"},
	}
	out := []map[string]any{}
	for _, row := range rows {
		for _, def := range defs {
			price, hasPrice := moneyOrNil(row[def.price])
			setup, hasSetup := moneyOrNil(row[def.setupFee])
			if !hasPrice && !hasSetup {
				continue
			}
			out = append(out, map[string]any{"cycle": def.key, "label": def.label, "price": price, "setup_fee": setup, "currency": row["code"]})
		}
	}
	return out
}

func moneyOrNil(value any) (any, bool) {
	text := strings.TrimSpace(fmt.Sprint(value))
	if text == "" || text == "<nil>" {
		return nil, false
	}
	number, err := strconv.ParseFloat(text, 64)
	if err != nil || number < 0 {
		return nil, false
	}
	return number, true
}

func stringOrNil(value any) any {
	text := firstString(value)
	if text == "" {
		return nil
	}
	return text
}

func nilIfEmpty(value string) any {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	return value
}

func truthyAny(value any) bool {
	switch v := value.(type) {
	case bool:
		return v
	case int:
		return v != 0
	case int64:
		return v != 0
	case float64:
		return v != 0
	case string:
		text := strings.ToLower(strings.TrimSpace(v))
		return text == "1" || text == "true" || text == "yes" || text == "on" || text == "required"
	default:
		return false
	}
}
