package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
)

// Config structs for version_groups.json
type VersionGroupInfo struct {
	Versions     []string             `json:"versions"`
	Generation   int                  `json:"generation"`
	Region       string               `json:"region"`
	RegionID     int                  `json:"region_id"`
	ExtraRegions []int                `json:"extra_regions"`
	Games        map[string]GameInfo  `json:"games"`
}

type GameInfo struct {
	Name        string  `json:"name"`
	Slug        string  `json:"slug"`
	ReleaseYear int     `json:"release_year"`
	Color       *string `json:"color"`
}

// Config structs for route_order.json
type RouteOrderFile struct {
	Routes  map[string][]string `json:"routes"`
	Aliases map[string]string   `json:"aliases"`
}

// Config structs for special_encounters.json
type SpecialEncountersFile struct {
	Encounters map[string]map[string][]EncounterOutput `json:"encounters"`
	Aliases    map[string]string                       `json:"aliases"`
}

func getSpecialEncounters(se *SpecialEncountersFile, vgKey string) map[string][]EncounterOutput {
	if se == nil {
		return nil
	}
	if data, ok := se.Encounters[vgKey]; ok {
		return data
	}
	if alias, ok := se.Aliases[vgKey]; ok {
		if data, ok := se.Encounters[alias]; ok {
			return data
		}
	}
	return nil
}

func main() {
	clearCache := flag.Bool("clear-cache", false, "Delete cached API responses before fetching")
	flag.Parse()

	pokeapiURL := os.Getenv("POKEAPI_URL")
	if pokeapiURL == "" {
		pokeapiURL = "http://localhost:8000/api/v2"
	}

	// Resolve paths relative to this tool's location or use the standard layout
	seedsDir := findSeedsDir()
	dataDir := filepath.Join(seedsDir, "data")
	cacheDir := filepath.Join(seedsDir, ".pokeapi_cache")

	client := NewClient(pokeapiURL, cacheDir, 50)

	if *clearCache {
		if err := client.ClearCache(); err != nil {
			fmt.Fprintf(os.Stderr, "Warning: could not clear cache: %v\n", err)
		} else {
			fmt.Println("Cleared API cache.")
		}
	}

	ctx := context.Background()

	// Connectivity check
	fmt.Printf("Connecting to PokeAPI at %s...\n", pokeapiURL)
	if _, err := client.Get(ctx, "pokemon-species/1"); err != nil {
		fmt.Fprintf(os.Stderr, "Error: Cannot connect to PokeAPI at %s\n  %v\nStart the local PokeAPI server or set POKEAPI_URL.\n", pokeapiURL, err)
		os.Exit(1)
	}

	// Load configs
	versionGroups, err := loadJSON[map[string]VersionGroupInfo](filepath.Join(seedsDir, "version_groups.json"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error loading version_groups.json: %v\n", err)
		os.Exit(1)
	}

	routeOrder, err := loadRouteOrder(filepath.Join(seedsDir, "route_order.json"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error loading route_order.json: %v\n", err)
		os.Exit(1)
	}

	specialEnc, err := loadJSON[SpecialEncountersFile](filepath.Join(seedsDir, "special_encounters.json"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Warning: could not load special_encounters.json: %v\n", err)
		// Continue without special encounters
	}

	if err := os.MkdirAll(dataDir, 0o755); err != nil {
		fmt.Fprintf(os.Stderr, "Error creating data dir: %v\n", err)
		os.Exit(1)
	}

	// Build games.json
	var games []GameOutput
	for _, vgInfo := range *versionGroups {
		for _, gameInfo := range vgInfo.Games {
			games = append(games, GameOutput{
				Name:        gameInfo.Name,
				Slug:        gameInfo.Slug,
				Generation:  vgInfo.Generation,
				Region:      vgInfo.Region,
				ReleaseYear: gameInfo.ReleaseYear,
				Color:       gameInfo.Color,
			})
		}
	}

	writeJSON(filepath.Join(dataDir, "games.json"), games)
	fmt.Printf("Wrote %d games to games.json\n", len(games))

	// Process each version
	pokeIDCollector := NewPokeIDCollector()

	for vgKey, vgInfo := range *versionGroups {
		if vgInfo.RegionID == 0 {
			fmt.Printf("\nSkipping %s (no PokeAPI region, data managed manually)\n", vgKey)
			continue
		}
		for _, verName := range vgInfo.Versions {
			routes, err := processVersion(ctx, client, verName, vgInfo, vgKey, routeOrder, specialEnc, pokeIDCollector)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error processing %s: %v\n", verName, err)
				os.Exit(1)
			}
			writeJSON(filepath.Join(dataDir, verName+".json"), routes)
		}
	}

	// Fetch all species data (reused for pokemon discovery + evolutions)
	speciesData, err := fetchAllSpecies(ctx, client)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error fetching species: %v\n", err)
		os.Exit(1)
	}

	// Fetch all Pokemon (base + all forms)
	pokemonList, err := fetchAllPokemon(ctx, client, speciesData, pokeIDCollector.IDs())
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error fetching pokemon: %v\n", err)
		os.Exit(1)
	}
	writeJSON(filepath.Join(dataDir, "pokemon.json"), pokemonList)
	fmt.Printf("\nWrote %d Pokemon to pokemon.json\n", len(pokemonList))

	// Build set of all seeded PokeAPI IDs for evolution filtering
	allSeededDex := make(map[int]bool)
	for _, p := range pokemonList {
		allSeededDex[p.PokeAPIID] = true
	}

	// Fetch evolution chains
	evolutions, err := fetchEvolutionData(ctx, client, speciesData, allSeededDex)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error fetching evolutions: %v\n", err)
		os.Exit(1)
	}

	evolutions, err = applyEvolutionOverrides(evolutions, filepath.Join(dataDir, "evolution_overrides.json"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error applying evolution overrides: %v\n", err)
		os.Exit(1)
	}

	writeJSON(filepath.Join(dataDir, "evolutions.json"), evolutions)
	fmt.Printf("\nWrote %d evolution pairs to evolutions.json\n", len(evolutions))

	fmt.Println("\nDone! JSON files written to seeds/data/")
	fmt.Println("Review route ordering and curate as needed.")
}

// findSeedsDir locates the backend/src/app/seeds directory.
func findSeedsDir() string {
	// Try relative to CWD (from repo root)
	candidates := []string{
		"backend/src/app/seeds",
		"../../backend/src/app/seeds", // from tools/fetch-pokeapi/
	}
	for _, c := range candidates {
		if _, err := os.Stat(filepath.Join(c, "version_groups.json")); err == nil {
			abs, _ := filepath.Abs(c)
			return abs
		}
	}
	// Fallback: try to find from executable location
	exe, _ := os.Executable()
	exeDir := filepath.Dir(exe)
	rel := filepath.Join(exeDir, "../../backend/src/app/seeds")
	if _, err := os.Stat(filepath.Join(rel, "version_groups.json")); err == nil {
		abs, _ := filepath.Abs(rel)
		return abs
	}
	// Default
	abs, _ := filepath.Abs("backend/src/app/seeds")
	return abs
}

func loadJSON[T any](path string) (*T, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var result T
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

func loadRouteOrder(path string) (map[string][]string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var rof RouteOrderFile
	if err := json.Unmarshal(data, &rof); err != nil {
		return nil, err
	}
	routes := make(map[string][]string)
	for k, v := range rof.Routes {
		routes[k] = v
	}
	for alias, target := range rof.Aliases {
		routes[alias] = routes[target]
	}
	return routes, nil
}

func writeJSON(path string, data interface{}) {
	content, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error marshaling JSON for %s: %v\n", path, err)
		os.Exit(1)
	}
	content = append(content, '\n')
	if err := os.WriteFile(path, content, 0o644); err != nil {
		fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, err)
		os.Exit(1)
	}
	fmt.Printf("  -> %s\n", path)
}
