package topdata

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

type legacyRacialtypesFeatTable struct {
	Output string
	Rows   []map[string]any
}

func importLegacyRacialtypes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) {
	if strings.TrimSpace(referenceBuilderDir) == "" {
		return 0, nil
	}

	legacyCoreDir := filepath.Join(referenceBuilderDir, "data", "racialtypes", "core")
	legacyFeatsDir := filepath.Join(referenceBuilderDir, "data", "racialtypes", "feats")
	if _, err := os.Stat(legacyCoreDir); err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, err
	}
	if _, err := os.Stat(legacyFeatsDir); err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, err
	}

	targetRoot := filepath.Join(dataDir, "racialtypes", "registry")
	targetBasePath := filepath.Join(targetRoot, "base.json")
	targetLockPath := filepath.Join(targetRoot, "lock.json")
	targetRacesDir := filepath.Join(targetRoot, "races")
	if fileExists(targetBasePath) && fileExists(targetLockPath) {
		entries, err := os.ReadDir(targetRacesDir)
		if err == nil {
			raceFiles := 0
			for _, entry := range entries {
				if !entry.IsDir() && strings.HasSuffix(strings.ToLower(entry.Name()), ".json") {
					raceFiles++
				}
			}
			if raceFiles == 16 {
				return 0, nil
			}
		}
	}

	baseColumns, baseRows, lockData, raceRows, err := collectLegacyRacialtypesRegistry(legacyCoreDir, legacyFeatsDir, legacyTLK)
	if err != nil {
		return 0, err
	}

	if err := removePathIfExists(filepath.Join(dataDir, "racialtypes", "core")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(dataDir, "racialtypes", "feats")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(targetRoot, "base_rows.json")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(targetRacesDir); err != nil {
		return 0, err
	}
	if err := os.MkdirAll(targetRacesDir, 0o755); err != nil {
		return 0, err
	}

	writes := []struct {
		path string
		obj  any
	}{
		{
			path: targetBasePath,
			obj: map[string]any{
				"columns": stringSliceToAny(baseColumns),
				"rows":    rowsToAny(baseRows),
			},
		},
		{
			path: targetLockPath,
			obj:  anyMapInt(lockData),
		},
	}

	raceKeys := make([]string, 0, len(raceRows))
	for key := range raceRows {
		raceKeys = append(raceKeys, key)
	}
	slices.Sort(raceKeys)
	for _, key := range raceKeys {
		fileName := strings.TrimPrefix(key, "racialtypes:") + ".json"
		writes = append(writes, struct {
			path string
			obj  any
		}{
			path: filepath.Join(targetRacesDir, fileName),
			obj:  raceRows[key],
		})
	}

	for _, write := range writes {
		raw, err := json.MarshalIndent(write.obj, "", "  ")
		if err != nil {
			return 0, err
		}
		raw = append(raw, '\n')
		if err := os.WriteFile(write.path, raw, 0o644); err != nil {
			return 0, err
		}
	}
	return len(writes), nil
}

func collectLegacyRacialtypesRegistry(coreDir, featsDir string, legacyTLK *legacyTLKData) ([]string, []map[string]any, map[string]int, map[string]map[string]any, error) {
	coreCollected, err := collectBaseDataset(nativeDataset{
		Name:       "racialtypes",
		BasePath:   filepath.Join(coreDir, "base.json"),
		LockPath:   filepath.Join(coreDir, "lock.json"),
		ModulesDir: filepath.Join(coreDir, "modules"),
		Spec:       specForDataset("racialtypes"),
	})
	if err != nil {
		return nil, nil, nil, nil, err
	}

	featTables, err := loadLegacyRacialtypesFeatTables(featsDir)
	if err != nil {
		return nil, nil, nil, nil, err
	}

	baseRows := make([]map[string]any, 0)
	raceRows := map[string]map[string]any{}
	lockData := map[string]int{}
	for key, id := range coreCollected.LockData {
		if strings.HasPrefix(key, "racialtypes:") {
			lockData[key] = id
		}
	}

	for _, collectedRow := range coreCollected.Rows {
		row, ok := deepCopyValue(collectedRow).(map[string]any)
		if !ok {
			continue
		}
		if legacyTLK != nil {
			if _, _, err := inlineLegacyTLKValue(row, legacyTLK); err != nil {
				return nil, nil, nil, nil, err
			}
		}
		key, _ := row["key"].(string)
		if !strings.HasPrefix(key, "racialtypes:") {
			assignRacialtypesBaseRowKey(row)
			if key, _ := row["key"].(string); key != "" {
				rowID, err := asInt(row["id"])
				if err != nil {
					return nil, nil, nil, nil, err
				}
				lockData[key] = rowID
			}
			baseRows = append(baseRows, row)
			continue
		}

		delete(row, "id")
		delete(row, "key")

		raceFile := map[string]any{
			"key":  key,
			"core": row,
		}
		if tableKey := extractRacialtypesFeatTableKey(row["FeatsTable"]); tableKey != "" {
			table, ok := featTables[tableKey]
			if !ok {
				return nil, nil, nil, nil, fmt.Errorf("%s: unknown feat table %s", key, tableKey)
			}
			delete(row, "FeatsTable")
			raceFile["feat_output"] = table.Output
			raceFile["feats"] = rowsToAny(table.Rows)
		}
		raceRows[key] = raceFile
	}

	slices.SortFunc(baseRows, func(a, b map[string]any) int {
		left, _ := asInt(a["id"])
		right, _ := asInt(b["id"])
		return left - right
	})
	return coreCollected.Columns, baseRows, lockData, raceRows, nil
}

func loadLegacyRacialtypesFeatTables(featsDir string) (map[string]legacyRacialtypesFeatTable, error) {
	entries, err := os.ReadDir(featsDir)
	if err != nil {
		return nil, err
	}

	tables := map[string]legacyRacialtypesFeatTable{}
	for _, entry := range entries {
		if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
			continue
		}
		obj, err := loadJSONObject(filepath.Join(featsDir, entry.Name()))
		if err != nil {
			return nil, err
		}
		tableKey, _ := obj["key"].(string)
		outputName, _ := obj["output"].(string)
		rawRows, ok := obj["rows"].([]any)
		if tableKey == "" || outputName == "" || !ok {
			return nil, fmt.Errorf("%s: racialtypes feat table must define key, output, and rows", entry.Name())
		}
		rows := make([]map[string]any, 0, len(rawRows))
		for index, raw := range rawRows {
			row, ok := raw.(map[string]any)
			if !ok {
				return nil, fmt.Errorf("%s: row %d must be an object", entry.Name(), index)
			}
			rows = append(rows, row)
		}
		tables[tableKey] = legacyRacialtypesFeatTable{
			Output: outputName,
			Rows:   rows,
		}
	}
	return tables, nil
}

func extractRacialtypesFeatTableKey(value any) string {
	obj, ok := value.(map[string]any)
	if !ok {
		return ""
	}
	tableKey, _ := obj["table"].(string)
	return tableKey
}

func assignRacialtypesBaseRowKey(row map[string]any) {
	name, _ := row["Name"].(string)
	if strings.TrimSpace(name) == "" || name == nullValue {
		delete(row, "key")
		return
	}
	label, _ := row["Label"].(string)
	keySuffix := normalizeRacialtypesKeySuffix(label)
	if keySuffix == "" {
		delete(row, "key")
		return
	}
	row["key"] = "racialtypes:" + keySuffix
}

func normalizeRacialtypesKeySuffix(label string) string {
	normalized := strings.ToLower(strings.TrimSpace(label))
	if normalized == "" {
		return ""
	}
	var b strings.Builder
	lastUnderscore := false
	for _, ch := range normalized {
		isAlphaNum := (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')
		if isAlphaNum {
			b.WriteRune(ch)
			lastUnderscore = false
			continue
		}
		if lastUnderscore {
			continue
		}
		b.WriteByte('_')
		lastUnderscore = true
	}
	return strings.Trim(b.String(), "_")
}
