package commerce

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

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

var publicPluginKeys = map[string]struct{}{
	"schema_version":        {},
	"id":                    {},
	"type":                  {},
	"name":                  {},
	"version":               {},
	"status":                {},
	"description":           {},
	"capabilities":          {},
	"manage_actions":        {},
	"test_links":            {},
	"product_page":          {},
	"manage_page":           {},
	"upstream_param_fields": {},
}

var publicPageKeys = map[string]struct{}{
	"tabs":             {},
	"layout":           {},
	"summary_html":     {},
	"empty_state_html": {},
	"purchase_fields":  {},
}

func publicProvisionManifest(pluginID string) (map[string]any, error) {
	pluginID = strings.TrimSpace(pluginID)
	if pluginID == "" || !safePluginID(pluginID) {
		return nil, fmt.Errorf("invalid plugin id")
	}

	path, err := provisionPluginManifestPath(pluginID)
	if err != nil {
		return nil, err
	}
	raw, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var manifest map[string]any
	if err := json.Unmarshal(raw, &manifest); err != nil {
		return nil, err
	}
	if manifest["type"] != "provision" {
		return nil, fmt.Errorf("plugin %s is not a provision plugin", pluginID)
	}
	manifest["id"] = pluginID
	if _, ok := manifest["name"]; !ok {
		manifest["name"] = pluginID
	}
	if _, ok := manifest["version"]; !ok {
		manifest["version"] = "1.0.0"
	}
	if _, ok := manifest["capabilities"]; !ok {
		manifest["capabilities"] = []any{}
	}
	manifest["product_page"] = normalizePluginPage(manifest, "product_page", filepath.Dir(path))

	public := map[string]any{}
	for key := range publicPluginKeys {
		if value, ok := manifest[key]; ok {
			public[key] = value
		}
	}
	if page, ok := public["product_page"].(map[string]any); ok {
		public["product_page"] = filterPluginPage(page)
	} else {
		public["product_page"] = map[string]any{"tabs": []any{}, "purchase_fields": []any{}}
	}
	return public, nil
}

func publicGoPluginManifest(manifest plugins.Manifest) map[string]any {
	provider := strings.TrimSpace(manifest.Provider)
	if provider == "" {
		provider = "plugin:" + manifest.ID
	}
	status := "active"
	if !manifest.Enabled {
		status = "disabled"
	}
	out := map[string]any{
		"schema_version": manifest.SchemaVersion,
		"id":             manifest.ID,
		"type":           plugins.CapabilityProvision,
		"name":           manifest.Name,
		"version":        manifest.Version,
		"status":         status,
		"description":    manifest.Description,
		"capabilities":   manifest.Capabilities,
		"runtime":        manifest.Runtime,
		"provider":       provider,
		"manage_actions": manifest.ManageActions,
		"test_links":     manifest.TestLinks,
		"manage_page":    manifest.ManagePage,
		"requirements":   manifest.Requirements,
		"docs":           manifest.Docs,
		"author":         manifest.Author,
		"enabled":        manifest.Enabled,
	}
	if manifest.ConfigSchema != nil {
		out["config_schema"] = manifest.ConfigSchema
		out["config_fields"] = pluginConfigFields(manifest.ConfigSchema)
	}
	if manifest.UpstreamParamFields == nil {
		out["upstream_param_fields"] = []any{}
	} else {
		out["upstream_param_fields"] = manifest.UpstreamParamFields
	}
	if manifest.ProductPage == nil {
		out["product_page"] = map[string]any{"tabs": []any{}, "purchase_fields": []any{}}
	} else {
		page := normalizePluginPage(map[string]any{"product_page": manifest.ProductPage}, "product_page", manifest.Dir)
		out["product_page"] = filterPluginPage(page)
	}
	return out
}

func userVisiblePluginManageActions(manifest map[string]any) []any {
	out := []any{}
	for _, item := range asSlice(manifest["manage_actions"]) {
		action, ok := item.(map[string]any)
		if !ok {
			continue
		}
		if strings.EqualFold(strings.TrimSpace(fmt.Sprint(action["audience"])), "admin") {
			continue
		}
		out = append(out, action)
	}
	return out
}

func safePluginID(value string) bool {
	if value == "" {
		return false
	}
	for _, r := range value {
		if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '_' || r == '-' {
			continue
		}
		return false
	}
	return true
}

func provisionPluginManifestPath(pluginID string) (string, error) {
	candidates := []string{
		filepath.Join("..", "backend", "mode", "provision", pluginID, "plugin.json"),
		filepath.Join("backend", "mode", "provision", pluginID, "plugin.json"),
		filepath.Join("..", "..", "backend", "mode", "provision", pluginID, "plugin.json"),
	}
	for _, candidate := range candidates {
		path, err := filepath.Abs(candidate)
		if err != nil {
			continue
		}
		if _, err := os.Stat(path); err == nil {
			return path, nil
		}
	}
	return "", fmt.Errorf("provision plugin %s not found", pluginID)
}

func normalizePluginPage(manifest map[string]any, key string, pluginDir string) map[string]any {
	page, ok := manifest[key].(map[string]any)
	if !ok {
		return map[string]any{"layout": "tabs", "tabs": []any{}, "purchase_fields": []any{}}
	}
	out := cloneMap(page)
	if file, ok := out["summary_html_file"].(string); ok && out["summary_html"] == nil {
		out["summary_html"] = readPluginText(pluginDir, file)
	}
	if file, ok := out["empty_state_html_file"].(string); ok && out["empty_state_html"] == nil {
		out["empty_state_html"] = readPluginText(pluginDir, file)
	}

	tabs := []any{}
	for _, item := range asSlice(out["tabs"]) {
		tab, ok := item.(map[string]any)
		if !ok {
			continue
		}
		next := cloneMap(tab)
		if file, ok := next["html_file"].(string); ok && next["html"] == nil {
			next["html"] = readPluginText(pluginDir, file)
		}
		if next["html"] == nil {
			next["html"] = ""
		}
		if next["key"] != nil && next["label"] != nil {
			tabs = append(tabs, next)
		}
	}
	out["tabs"] = tabs

	fields := []any{}
	for _, item := range asSlice(out["purchase_fields"]) {
		field, ok := item.(map[string]any)
		if !ok || field["key"] == nil || field["label"] == nil {
			continue
		}
		next := cloneMap(field)
		if next["type"] == nil {
			next["type"] = "text"
		}
		if next["required"] == nil {
			next["required"] = false
		}
		fields = append(fields, next)
	}
	out["purchase_fields"] = fields
	if out["layout"] == nil {
		out["layout"] = "tabs"
	}
	return out
}

func filterPluginPage(page map[string]any) map[string]any {
	out := map[string]any{}
	for key := range publicPageKeys {
		if value, ok := page[key]; ok {
			out[key] = value
		}
	}
	if out["tabs"] == nil {
		out["tabs"] = []any{}
	}
	if out["purchase_fields"] == nil {
		out["purchase_fields"] = []any{}
	}
	return out
}

func readPluginText(pluginDir string, relative string) string {
	if strings.TrimSpace(relative) == "" || filepath.IsAbs(relative) {
		return ""
	}
	path := filepath.Clean(filepath.Join(pluginDir, relative))
	root := filepath.Clean(pluginDir)
	if !strings.HasPrefix(path, root+string(os.PathSeparator)) {
		return ""
	}
	raw, err := os.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(raw)
}

func cloneMap(in map[string]any) map[string]any {
	out := make(map[string]any, len(in))
	for key, value := range in {
		out[key] = value
	}
	return out
}

func asSlice(value any) []any {
	switch items := value.(type) {
	case []any:
		return items
	case []map[string]any:
		out := make([]any, 0, len(items))
		for _, item := range items {
			out = append(out, item)
		}
		return out
	case []string:
		out := make([]any, 0, len(items))
		for _, item := range items {
			out = append(out, item)
		}
		return out
	}
	return []any{}
}
