package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
)

// fetchEvolutionData fetches evolution chains and returns flattened pairs.
func fetchEvolutionData(
	ctx context.Context,
	client *Client,
	speciesData map[int]*SpeciesResp,
	seededDex map[int]bool,
) ([]EvolutionOutput, error) {
	fmt.Println("\n--- Fetching evolution chains ---")

	// Extract unique chain IDs from species data
	chainIDSet := make(map[int]bool)
	for sid, species := range speciesData {
		if seededDex[sid] {
			chainIDSet[species.EvolutionChain.ID()] = true
		}
	}

	chainIDs := make([]int, 0, len(chainIDSet))
	for id := range chainIDSet {
		chainIDs = append(chainIDs, id)
	}
	sort.Ints(chainIDs)

	fmt.Printf("  Found %d unique evolution chains\n", len(chainIDs))

	// Fetch chains concurrently
	type chainResult struct {
		chain EvolutionChainResp
	}
	results := make([]chainResult, len(chainIDs))
	var wg sync.WaitGroup
	errs := make([]error, len(chainIDs))

	for i, cid := range chainIDs {
		wg.Add(1)
		go func(i, cid int) {
			defer wg.Done()
			data, err := client.Get(ctx, fmt.Sprintf("evolution-chain/%d", cid))
			if err != nil {
				errs[i] = err
				return
			}
			if err := json.Unmarshal(data, &results[i].chain); err != nil {
				errs[i] = fmt.Errorf("parsing evolution chain %d: %w", cid, err)
			}
		}(i, cid)
	}
	wg.Wait()

	for _, err := range errs {
		if err != nil {
			return nil, err
		}
	}

	// Flatten all chains
	var allPairs []EvolutionOutput
	type dedupeKey struct {
		from, to int
		trigger  string
	}
	seen := make(map[dedupeKey]bool)

	for _, r := range results {
		pairs := flattenChain(r.chain.Chain, seededDex)
		for _, p := range pairs {
			key := dedupeKey{p.FromPokeAPIID, p.ToPokeAPIID, p.Trigger}
			if !seen[key] {
				seen[key] = true
				allPairs = append(allPairs, p)
			}
		}
	}

	sort.Slice(allPairs, func(i, j int) bool {
		if allPairs[i].FromPokeAPIID != allPairs[j].FromPokeAPIID {
			return allPairs[i].FromPokeAPIID < allPairs[j].FromPokeAPIID
		}
		return allPairs[i].ToPokeAPIID < allPairs[j].ToPokeAPIID
	})

	fmt.Printf("  Total evolution pairs: %d\n", len(allPairs))
	return allPairs, nil
}

// flattenChain recursively flattens an evolution chain into (from, to) pairs.
func flattenChain(chain ChainLink, seededDex map[int]bool) []EvolutionOutput {
	var pairs []EvolutionOutput
	fromDex := chain.Species.ID()

	for _, evo := range chain.EvolvesTo {
		toDex := evo.Species.ID()

		for _, detail := range evo.EvolutionDetails {
			trigger := detail.Trigger.Name

			var minLevel *int
			if detail.MinLevel != nil {
				v := *detail.MinLevel
				minLevel = &v
			}

			var item *string
			if detail.Item != nil {
				s := detail.Item.Name
				item = &s
			}

			var heldItem *string
			if detail.HeldItem != nil {
				s := detail.HeldItem.Name
				heldItem = &s
			}

			conditions := CollectEvolutionConditions(detail)
			var condition *string
			if len(conditions) > 0 {
				s := strings.Join(conditions, ", ")
				condition = &s
			}

			if seededDex[fromDex] && seededDex[toDex] {
				pairs = append(pairs, EvolutionOutput{
					FromPokeAPIID: fromDex,
					ToPokeAPIID:   toDex,
					Trigger:       trigger,
					MinLevel:      minLevel,
					Item:          item,
					HeldItem:      heldItem,
					Condition:     condition,
				})
			}
		}

		// Recurse
		pairs = append(pairs, flattenChain(evo, seededDex)...)
	}

	return pairs
}

// EvolutionOverrides represents the evolution_overrides.json structure.
type EvolutionOverrides struct {
	Remove []struct {
		FromDex int `json:"from_dex"`
		ToDex   int `json:"to_dex"`
	} `json:"remove"`
	Add []struct {
		FromDex   int     `json:"from_dex"`
		ToDex     int     `json:"to_dex"`
		Trigger   string  `json:"trigger"`
		MinLevel  *int    `json:"min_level"`
		Item      *string `json:"item"`
		HeldItem  *string `json:"held_item"`
		Condition *string `json:"condition"`
		Region    *string `json:"region"`
	} `json:"add"`
	Modify []struct {
		FromDex int               `json:"from_dex"`
		ToDex   int               `json:"to_dex"`
		Set     map[string]interface{} `json:"set"`
	} `json:"modify"`
}

// applyEvolutionOverrides applies overrides from evolution_overrides.json.
func applyEvolutionOverrides(evolutions []EvolutionOutput, overridesPath string) ([]EvolutionOutput, error) {
	data, err := os.ReadFile(overridesPath)
	if err != nil {
		if os.IsNotExist(err) {
			return evolutions, nil
		}
		return nil, fmt.Errorf("reading evolution overrides: %w", err)
	}

	var overrides EvolutionOverrides
	if err := json.Unmarshal(data, &overrides); err != nil {
		return nil, fmt.Errorf("parsing evolution overrides: %w", err)
	}

	// Remove entries
	for _, removal := range overrides.Remove {
		filtered := evolutions[:0]
		for _, e := range evolutions {
			if !(e.FromPokeAPIID == removal.FromDex && e.ToPokeAPIID == removal.ToDex) {
				filtered = append(filtered, e)
			}
		}
		evolutions = filtered
	}

	// Add entries
	for _, addition := range overrides.Add {
		trigger := addition.Trigger
		if trigger == "" {
			trigger = "level-up"
		}
		evolutions = append(evolutions, EvolutionOutput{
			FromPokeAPIID: addition.FromDex,
			ToPokeAPIID:   addition.ToDex,
			Trigger:       trigger,
			MinLevel:      addition.MinLevel,
			Item:          addition.Item,
			HeldItem:      addition.HeldItem,
			Condition:     addition.Condition,
			Region:        addition.Region,
		})
	}

	// Modify entries
	for _, mod := range overrides.Modify {
		for i := range evolutions {
			e := &evolutions[i]
			if e.FromPokeAPIID == mod.FromDex && e.ToPokeAPIID == mod.ToDex {
				for key, value := range mod.Set {
					switch key {
					case "trigger":
						if s, ok := value.(string); ok {
							e.Trigger = s
						}
					case "min_level":
						if v, ok := value.(float64); ok {
							level := int(v)
							e.MinLevel = &level
						} else if value == nil {
							e.MinLevel = nil
						}
					case "item":
						if s, ok := value.(string); ok {
							e.Item = &s
						} else if value == nil {
							e.Item = nil
						}
					case "held_item":
						if s, ok := value.(string); ok {
							e.HeldItem = &s
						} else if value == nil {
							e.HeldItem = nil
						}
					case "condition":
						if s, ok := value.(string); ok {
							e.Condition = &s
						} else if value == nil {
							e.Condition = nil
						}
					case "region":
						if s, ok := value.(string); ok {
							e.Region = &s
						} else if value == nil {
							e.Region = nil
						}
					}
				}
			}
		}
	}

	// Re-sort
	sort.Slice(evolutions, func(i, j int) bool {
		if evolutions[i].FromPokeAPIID != evolutions[j].FromPokeAPIID {
			return evolutions[i].FromPokeAPIID < evolutions[j].FromPokeAPIID
		}
		return evolutions[i].ToPokeAPIID < evolutions[j].ToPokeAPIID
	})

	fmt.Printf("  Applied overrides: %d pairs after overrides\n", len(evolutions))
	return evolutions, nil
}
