package commerce

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"

	"qycw/go-server/internal/plugins"
)

func (s *Service) AdminTestProvisionInterface(ctx context.Context, interfaceID int64) (map[string]any, error) {
	row, err := rawByID(ctx, s.db, "provision_interfaces", interfaceID)
	if err != nil {
		return nil, err
	}
	if enabled, ok := row["enabled"].(bool); ok && !enabled {
		return nil, BadRequest("provision interface is disabled")
	}
	pluginID := stringFromMap(row, "plugin_id")
	config := provisionInterfaceRuntimeConfig(row["config"])
	result, err := s.AdminTestProvisionPlugin(ctx, pluginID, map[string]any{"config": config})
	ok := err == nil && result["ok"] != false
	message := "test passed"
	if err != nil {
		message = err.Error()
	} else if text := firstString(result["message"]); text != "" {
		message = text
	}
	_ = s.db.WithContext(ctx).Table("provision_interfaces").Where("id = ?", interfaceID).Updates(map[string]any{
		"last_test_ok":      ok,
		"last_test_message": trimText(message, 255),
		"updated_at":        time.Now().UTC(),
	}).Error
	if err != nil {
		return nil, err
	}
	result["interface_id"] = interfaceID
	return result, nil
}

func (s *Service) AdminTestProvisionPlugin(ctx context.Context, pluginID string, payload map[string]any) (map[string]any, error) {
	if s.plugins == nil {
		return nil, BadRequest("plugin runtime is not enabled")
	}
	pluginID = strings.TrimSpace(pluginID)
	if pluginID == "" {
		return nil, BadRequest("plugin_id is required")
	}
	config := map[string]any{}
	hasNestedConfig := false
	if nested, ok := payload["config"].(map[string]any); ok {
		config = mergeMapsAny(config, nested)
		hasNestedConfig = true
	}
	if nested, ok := payload["values"].(map[string]any); ok {
		config = mergeMapsAny(config, nested)
		hasNestedConfig = true
	}
	if !hasNestedConfig {
		for key, value := range payload {
			switch key {
			case "provision_interface_id", "interface_id":
				continue
			default:
				config[key] = value
			}
		}
	}
	if interfaceID, ok := int64FromAny(firstNonNil(payload["provision_interface_id"], payload["interface_id"])); ok {
		row, err := rawByID(ctx, s.db, "provision_interfaces", interfaceID)
		if err != nil {
			return nil, err
		}
		if rowPluginID := stringFromMap(row, "plugin_id"); rowPluginID != "" && rowPluginID != pluginID {
			return nil, BadRequest("provision interface belongs to a different plugin")
		}
		config = mergeMapsAny(provisionInterfaceRuntimeConfig(row["config"]), config)
	}
	config = s.plugins.MergeConfig(ctx, pluginID, config)
	manifest, err := s.plugins.Manifest(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	if !manifest.HasCapability(plugins.CapabilityProvision) {
		return nil, BadRequest("plugin does not provide provision capability")
	}
	var probe map[string]any
	err = s.plugins.Call(ctx, pluginID, plugins.CapabilityProvision, plugins.MethodProvisionProductFields, map[string]any{
		"config":           config,
		"interface_config": config,
		"admin_test":       true,
	}, &probe)
	if err != nil {
		return nil, err
	}
	if probe == nil {
		probe = map[string]any{}
	}
	if _, exists := probe["ok"]; !exists {
		probe["ok"] = true
	}
	probe["plugin"] = adminPluginManifestMap(manifest)
	return probe, nil
}

func LoadProvisionInterfaceRuntimeConfig(ctx context.Context, db *gorm.DB, interfaceID *int64, pluginID string) (map[string]any, error) {
	if interfaceID == nil {
		return map[string]any{}, nil
	}
	if db == nil {
		return nil, fmt.Errorf("database is required to load provision interface")
	}
	row, err := rawByID(ctx, db, "provision_interfaces", *interfaceID)
	if err != nil {
		return nil, err
	}
	if configuredPluginID := stringFromMap(row, "plugin_id"); configuredPluginID != "" && configuredPluginID != pluginID {
		return nil, fmt.Errorf("provision interface %d belongs to plugin %s, not %s", *interfaceID, configuredPluginID, pluginID)
	}
	if enabled, exists := row["enabled"]; exists && !truthy(enabled) {
		return nil, fmt.Errorf("provision interface %d is disabled", *interfaceID)
	}
	return provisionInterfaceRuntimeConfig(row["config"]), nil
}

func (s *Service) prepareProvisionInterfacePayload(ctx context.Context, id int64, payload map[string]any) (map[string]any, error) {
	out := cloneMap(payload)
	rawConfig, hasConfig := out["config"]
	if !hasConfig {
		return out, nil
	}
	pluginID := strings.TrimSpace(fmt.Sprint(out["plugin_id"]))
	currentConfig := map[string]any{}
	if id > 0 {
		current, err := rawByID(ctx, s.db, "provision_interfaces", id)
		if err != nil {
			return nil, err
		}
		if pluginID == "" || pluginID == "<nil>" {
			pluginID = stringFromMap(current, "plugin_id")
		}
		currentConfig = provisionInterfaceRuntimeConfig(current["config"])
	}
	values := parseJSONMap(rawConfig)
	secretKeys := s.provisionPluginSecretKeys(ctx, pluginID)
	for _, key := range secretKeys {
		value := strings.TrimSpace(fmt.Sprint(values[key]))
		if value == "***" {
			values[key] = currentConfig[key]
			value = strings.TrimSpace(fmt.Sprint(values[key]))
		}
		if value == "" || value == "<nil>" {
			values[key] = ""
			continue
		}
		encrypted, err := EncryptStoredSecret(value)
		if err != nil {
			return nil, err
		}
		values[key] = encrypted
	}
	out["config"] = jsonRawFromAny(map[string]any{"values": values, "secret_keys": secretKeys})
	return out, nil
}

func (s *Service) provisionPluginSecretKeys(ctx context.Context, pluginID string) []string {
	if s.plugins == nil || strings.TrimSpace(pluginID) == "" {
		return nil
	}
	manifest, err := s.plugins.Manifest(ctx, pluginID)
	if err != nil {
		return nil
	}
	keys := []string{}
	for _, item := range asSlice(pluginConfigFields(manifest.ConfigSchema)) {
		field, ok := item.(map[string]any)
		if !ok || !truthy(field["secret"]) {
			continue
		}
		if key := strings.TrimSpace(fmt.Sprint(field["key"])); key != "" && key != "<nil>" {
			keys = append(keys, key)
		}
	}
	return keys
}

func (s *Service) redactProvisionInterfaceRow(ctx context.Context, row map[string]any) {
	values := provisionInterfaceRuntimeConfig(row["config"])
	for _, key := range s.provisionPluginSecretKeys(ctx, stringFromMap(row, "plugin_id")) {
		if value := strings.TrimSpace(fmt.Sprint(values[key])); value != "" && value != "<nil>" {
			values[key] = "***"
		}
	}
	row["config"] = values
}

func provisionInterfaceRuntimeConfig(value any) map[string]any {
	stored := parseJSONMap(value)
	values, _ := stored["values"].(map[string]any)
	if values == nil {
		values = stored
	}
	secretKeys := map[string]struct{}{}
	for _, item := range valuesFromAny(stored["secret_keys"]) {
		if key := firstString(item); key != "" {
			secretKeys[key] = struct{}{}
		}
	}
	out := map[string]any{}
	for key, value := range values {
		if _, secret := secretKeys[key]; secret {
			if plain, err := decryptStoredSecret(firstString(value)); err == nil {
				out[key] = plain
				continue
			}
		}
		out[key] = value
	}
	return out
}

func parseJSONMap(value any) map[string]any {
	switch typed := value.(type) {
	case nil:
		return map[string]any{}
	case map[string]any:
		return typed
	case []byte:
		var out map[string]any
		if err := json.Unmarshal(typed, &out); err == nil && out != nil {
			return out
		}
	case string:
		var out map[string]any
		if err := json.Unmarshal([]byte(typed), &out); err == nil && out != nil {
			return out
		}
	default:
		raw, _ := json.Marshal(typed)
		var out map[string]any
		if err := json.Unmarshal(raw, &out); err == nil && out != nil {
			return out
		}
	}
	return map[string]any{}
}

func valuesFromAny(value any) []any {
	switch typed := value.(type) {
	case []any:
		return typed
	case []string:
		out := make([]any, 0, len(typed))
		for _, item := range typed {
			out = append(out, item)
		}
		return out
	default:
		return nil
	}
}
