package topdata

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

	"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
)

type NormalizeResult struct {
	StatePath           string
	ScannedFiles        int
	UpdatedFiles        int
	InlineValues        int
	RemainingFiles      int
	RemainingLegacyRefs int
}

func NormalizeProject(p *project.Project) (NormalizeResult, error) {
	if !p.HasTopData() {
		return NormalizeResult{}, fmt.Errorf("topdata is not configured for this project")
	}

	sourceDir := p.TopDataSourceDir()
	dataDir := filepath.Join(sourceDir, "data")
	migrationRoot := resolveMigrationInputRoot(sourceDir, p.TopDataReferenceBuilderDir())
	legacyTLK, err := loadLegacyTLK(filepath.Join(sourceDir, "tlk"))
	if err != nil {
		return NormalizeResult{}, err
	}
	if migrationRoot != "" {
		migrationTLK, err := loadLegacyTLK(filepath.Join(migrationRoot, "tlk"))
		if err != nil {
			return NormalizeResult{}, err
		}
		legacyTLK = mergeLegacyTLK(migrationTLK, legacyTLK)
	}
	baseDialogImported, err := importLegacyBaseDialog(migrationRoot, sourceDir)
	if err != nil {
		return NormalizeResult{}, err
	}

	itempropsImported, err := importLegacyItempropsRegistry(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	masterfeatsImported, err := importLegacyMasterfeats(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	featImported, err := importLegacyFeat(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	creaturespeedImported, err := importLegacyCreaturespeed(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	armorImported, err := importLegacyArmor(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	baseitemsImported, err := importLegacyBaseitems(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	damagetypesImported, err := importLegacyDamagetypes(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	skyboxesImported, err := importLegacySkyboxes(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	vfxPersistentImported, err := importLegacyVFXPersistent(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	loadscreensImported, err := importLegacyLoadscreens(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	progfxImported, err := importLegacyProgfx(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	genericdoorsImported, err := importLegacyGenericdoors(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	cloakmodelImported, err := importLegacyCloakmodel(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	repadjustImported, err := importLegacyRepadjust(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	wingmodelImported, err := importLegacyWingmodel(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	tailmodelImported, err := importLegacyTailmodel(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	doortypesImported, err := importLegacyDoortypes(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	visualeffectsImported, err := importLegacyVisualeffects(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	appearanceImported, err := importLegacyAppearance(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	placeablesImported, err := importLegacyPlaceables(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	rulesetImported, err := importLegacyRuleset(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	skillsImported, err := importLegacySkills(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	spellsImported, err := importLegacySpells(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	racialtypesImported, err := importLegacyRacialtypes(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	classesImported, err := importLegacyClasses(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}
	portraitsImported, err := importLegacyPortraits(migrationRoot, dataDir, legacyTLK)
	if err != nil {
		return NormalizeResult{}, err
	}

	paths, err := collectDataJSONPaths(dataDir)
	if err != nil {
		return NormalizeResult{}, err
	}

	result := NormalizeResult{
		StatePath:    filepath.Join(sourceDir, tlkStateFile),
		ScannedFiles: len(paths),
		UpdatedFiles: baseDialogImported + itempropsImported + masterfeatsImported + featImported + creaturespeedImported + armorImported + baseitemsImported + damagetypesImported + skyboxesImported + vfxPersistentImported + loadscreensImported + progfxImported + genericdoorsImported + cloakmodelImported + repadjustImported + wingmodelImported + tailmodelImported + doortypesImported + visualeffectsImported + appearanceImported + placeablesImported + rulesetImported + skillsImported + spellsImported + racialtypesImported + classesImported + portraitsImported,
	}
	state, err := loadTLKState(result.StatePath)
	if err != nil {
		return result, err
	}
	if state.Entries == nil {
		state.Entries = map[string]tlkStateMapping{}
	}
	if legacyTLK != nil && state.Language == "" {
		state.Language = legacyTLK.Language
	}
	if legacyTLK != nil {
		for key, id := range legacyTLK.Lock {
			if _, ok := state.Entries[key]; !ok {
				state.Entries[key] = tlkStateMapping{ID: id}
			}
		}
	}

	if legacyTLK != nil {
		for _, path := range paths {
			updated, count, err := inlineLegacyTLKFile(path, legacyTLK)
			if err != nil {
				return result, err
			}
			if updated {
				result.UpdatedFiles++
				result.InlineValues += count
			}
		}
	}

	if err := saveTLKState(result.StatePath, state); err != nil {
		return result, err
	}
	if err := removeLegacyTLKAuthoredInput(filepath.Join(sourceDir, "tlk")); err != nil {
		return result, err
	}
	remainingFiles, remainingRefs, err := countLegacyTLKRefs(paths)
	if err != nil {
		return result, err
	}
	result.RemainingFiles = remainingFiles
	result.RemainingLegacyRefs = remainingRefs
	return result, nil
}

func mergeLegacyTLK(base, override *legacyTLKData) *legacyTLKData {
	if base == nil && override == nil {
		return nil
	}
	merged := &legacyTLKData{
		Language: "en",
		Entries:  map[string]tlkEntryData{},
		Lock:     map[string]int{},
	}
	if base != nil {
		if base.Language != "" {
			merged.Language = base.Language
		}
		for key, entry := range base.Entries {
			merged.Entries[key] = entry
		}
		for key, id := range base.Lock {
			merged.Lock[key] = id
		}
	}
	if override != nil {
		if override.Language != "" {
			merged.Language = override.Language
		}
		for key, entry := range override.Entries {
			merged.Entries[key] = entry
		}
		for key, id := range override.Lock {
			merged.Lock[key] = id
		}
	}
	return merged
}

func removeLegacyTLKAuthoredInput(tlkDir string) error {
	modulesDir := filepath.Join(tlkDir, "modules")
	if err := os.RemoveAll(modulesDir); err != nil {
		return fmt.Errorf("remove %s: %w", modulesDir, err)
	}
	lockPath := filepath.Join(tlkDir, "lock.json")
	if err := os.Remove(lockPath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("remove %s: %w", lockPath, err)
	}
	gitkeepPath := filepath.Join(tlkDir, ".gitkeep")
	if err := os.Remove(gitkeepPath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("remove %s: %w", gitkeepPath, err)
	}
	if err := os.Remove(tlkDir); err != nil && !os.IsNotExist(err) {
		if !strings.Contains(err.Error(), "directory not empty") {
			return fmt.Errorf("remove %s: %w", tlkDir, err)
		}
	}
	return nil
}

func resolveMigrationInputRoot(sourceDir, referenceBuilderDir string) string {
	if strings.TrimSpace(referenceBuilderDir) != "" {
		return referenceBuilderDir
	}
	snapshotRoot := filepath.Join(sourceDir, "migration_snapshot")
	if info, err := os.Stat(snapshotRoot); err == nil && info.IsDir() {
		return snapshotRoot
	}
	return ""
}

func inlineLegacyTLKFile(path string, legacy *legacyTLKData) (bool, int, error) {
	obj, err := loadJSONObject(path)
	if err != nil {
		return false, 0, err
	}
	updated, count, err := inlineLegacyTLKValue(obj, legacy)
	if err != nil {
		return false, 0, err
	}
	if !updated {
		return false, 0, nil
	}
	raw, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		return false, 0, err
	}
	raw = append(raw, '\n')
	if err := os.WriteFile(path, raw, 0o644); err != nil {
		return false, 0, err
	}
	return true, count, nil
}

func inlineLegacyTLKValue(value any, legacy *legacyTLKData) (bool, int, error) {
	switch typed := value.(type) {
	case map[string]any:
		if rawTLK, ok := typed["tlk"]; ok {
			key, ok := rawTLK.(string)
			if ok {
				entry, ok := legacy.Entries[key]
				if !ok {
					return false, 0, nil
				}
				payload := map[string]any{
					"key":  key,
					"text": entry.Text,
				}
				if entry.SoundResRef != "" {
					payload["sound_resref"] = entry.SoundResRef
				}
				if entry.SoundLength != 0 {
					payload["sound_length"] = entry.SoundLength
				}
				typed["tlk"] = payload
				return true, 1, nil
			}
		}

		updated := false
		total := 0
		for key, child := range typed {
			childUpdated, childCount, err := inlineLegacyTLKValue(child, legacy)
			if err != nil {
				return false, 0, err
			}
			if childUpdated {
				typed[key] = child
				updated = true
				total += childCount
			}
		}
		return updated, total, nil
	case []any:
		updated := false
		total := 0
		for index, child := range typed {
			childUpdated, childCount, err := inlineLegacyTLKValue(child, legacy)
			if err != nil {
				return false, 0, err
			}
			if childUpdated {
				typed[index] = child
				updated = true
				total += childCount
			}
		}
		return updated, total, nil
	default:
		return false, 0, nil
	}
}

func collectDataJSONPaths(dataDir string) ([]string, error) {
	paths := make([]string, 0)
	err := filepath.WalkDir(dataDir, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		if strings.HasSuffix(strings.ToLower(d.Name()), ".json") && d.Name() != "lock.json" {
			paths = append(paths, path)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return paths, nil
}

func countLegacyTLKRefs(paths []string) (int, int, error) {
	files := 0
	refs := 0
	for _, path := range paths {
		obj, err := loadJSONObject(path)
		if err != nil {
			return 0, 0, err
		}
		count := countLegacyTLKRefsInValue(obj)
		if count == 0 {
			continue
		}
		files++
		refs += count
	}
	return files, refs, nil
}

func countLegacyTLKRefsInValue(value any) int {
	switch typed := value.(type) {
	case map[string]any:
		total := 0
		if rawTLK, ok := typed["tlk"]; ok {
			if _, ok := rawTLK.(string); ok {
				total++
			}
		}
		for _, child := range typed {
			total += countLegacyTLKRefsInValue(child)
		}
		return total
	case []any:
		total := 0
		for _, child := range typed {
			total += countLegacyTLKRefsInValue(child)
		}
		return total
	default:
		return 0
	}
}
