package topdata

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sort"
)

func importLegacyRuleset(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) {
	_ = legacyTLK

	legacyDir := filepath.Join(referenceBuilderDir, "data", "ruleset")
	if _, err := os.Stat(legacyDir); err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, err
	}

	targetDir := filepath.Join(dataDir, "ruleset")
	targetPath := filepath.Join(targetDir, "ruleset.json")
	if _, err := os.Stat(targetPath); err == nil {
		obj, err := loadJSONObject(targetPath)
		if err == nil && countLegacyTLKRefsInValue(obj) == 0 {
			return 0, nil
		}
	}

	baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json"))
	if err != nil {
		return 0, err
	}
	rows, err := mergeRulesetRows(baseObj, filepath.Join(legacyDir, "modules"))
	if err != nil {
		return 0, err
	}

	if err := os.MkdirAll(targetDir, 0o755); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(targetDir, "base.json")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(targetDir, "lock.json")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(targetDir, "modules")); err != nil {
		return 0, err
	}

	out := map[string]any{
		"output":  "ruleset.2da",
		"columns": baseObj["columns"],
		"rows":    rows,
	}
	raw, err := json.MarshalIndent(out, "", "  ")
	if err != nil {
		return 0, err
	}
	raw = append(raw, '\n')
	if err := os.WriteFile(targetPath, raw, 0o644); err != nil {
		return 0, err
	}
	return 1, nil
}

func mergeRulesetRows(baseObj map[string]any, modulesDir string) ([]any, error) {
	rawRows, ok := baseObj["rows"].([]any)
	if !ok {
		return nil, nil
	}

	rows := make([]map[string]any, 0, len(rawRows))
	byLabel := map[string]map[string]any{}
	for _, raw := range rawRows {
		row, ok := deepCopyValue(raw).(map[string]any)
		if !ok {
			continue
		}
		delete(row, "key")
		if rawID, ok := row["id"]; ok {
			id, err := asInt(rawID)
			if err != nil {
				return nil, err
			}
			row["id"] = id
		}
		if label, ok := row["Label"].(string); ok && label != "" && label != "****" {
			if _, exists := byLabel[label]; exists {
				return nil, fmt.Errorf("duplicate ruleset label %q", label)
			}
			byLabel[label] = row
		}
		rows = append(rows, row)
	}

	modulePaths, err := collectModulePaths(modulesDir)
	if err != nil {
		return nil, err
	}
	for _, path := range modulePaths {
		obj, err := loadJSONObject(path)
		if err != nil {
			return nil, err
		}
		overrideList, ok := obj["overrides"].([]any)
		if !ok {
			continue
		}
		for _, raw := range overrideList {
			override, ok := raw.(map[string]any)
			if !ok {
				continue
			}
			matchObj, ok := override["match"].(map[string]any)
			if !ok {
				return nil, fmt.Errorf("%s: ruleset override is missing match object", path)
			}
			rawLabel, ok := matchObj["Label"]
			if !ok {
				return nil, fmt.Errorf("%s: ruleset override match is missing Label", path)
			}
			labels, err := normalizeRulesetMatchLabels(rawLabel)
			if err != nil {
				return nil, fmt.Errorf("%s: %w", path, err)
			}
			for _, label := range labels {
				row, ok := byLabel[label]
				if !ok {
					return nil, fmt.Errorf("%s: ruleset override target label %q was not found", path, label)
				}
				for key, value := range override {
					if key == "id" || key == "key" || key == "_tlk" || isMetadataField(key) || key == "match" {
						continue
					}
					row[key] = deepCopyValue(value)
				}
			}
		}
	}

	sort.Slice(rows, func(i, j int) bool {
		return rows[i]["id"].(int) < rows[j]["id"].(int)
	})
	out := make([]any, 0, len(rows))
	for _, row := range rows {
		out = append(out, row)
	}
	return out, nil
}

func normalizeRulesetMatchLabels(raw any) ([]string, error) {
	switch typed := raw.(type) {
	case string:
		return []string{typed}, nil
	case []any:
		out := make([]string, 0, len(typed))
		for _, item := range typed {
			label, ok := item.(string)
			if !ok {
				return nil, fmt.Errorf("ruleset match.Label entries must be strings")
			}
			out = append(out, label)
		}
		return out, nil
	default:
		return nil, fmt.Errorf("ruleset match.Label must be a string or string array")
	}
}
