package main

import (
	"bufio"
	"encoding/csv"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
)

var projectRoot string

func init() {
	exePath, err := os.Executable()
	if err != nil {
		exePath, _ = os.Getwd()
	}
	exeDir := filepath.Dir(exePath)

	if _, err := os.Stat(filepath.Join(exeDir, "config.json")); err == nil {
		projectRoot = exeDir
	} else if _, err := os.Stat(filepath.Join(exeDir, "..", "config.json")); err == nil {
		projectRoot = filepath.Join(exeDir, "..")
	} else if cwd, err := os.Getwd(); err == nil {
		if _, err := os.Stat(filepath.Join(cwd, "config.json")); err == nil {
			projectRoot = cwd
		} else if _, err := os.Stat(filepath.Join(cwd, "..", "config.json")); err == nil {
			projectRoot = filepath.Join(cwd, "..")
		} else {
			projectRoot = cwd
		}
	}
}

func projectPath(parts ...string) string {
	all := append([]string{projectRoot}, parts...)
	return filepath.Join(all...)
}

type Config struct {
	SplitSize    int    `json:"split_size"`
	WorkerCount  int    `json:"worker_count"`
	Template     string `json:"template"`
	ExportFolder string `json:"export_folder"`
	DataFolder   string `json:"data_folder"`
	InjectFolder string `json:"inject_folder"`
	Export       struct {
		GenerateSitemap bool `json:"generate_sitemap"`
		GenerateIndex   bool `json:"generate_index"`
		GenerateAbout   bool `json:"generate_about"`
		RelatedCount    int  `json:"related_articles_count"`
	} `json:"export"`
	Memory struct {
		BatchSize int `json:"batch_size"`
	} `json:"memory"`
}

type Mapping struct {
	Bucket    string
	Foldering string
	Name      string
}

func main() {
	splitSize := flag.Int("split-size", 0, "Split size (articles per subfolder)")
	templateName := flag.String("template", "", "Template name")
	createCsv := flag.String("create-csv", "CREATE.csv", "Path to CREATE.csv")
	workers := flag.Int("workers", 0, "Number of parallel workers")
	interactive := flag.Bool("interactive", false, "Interactive mode with prompts")
	listTemplates := flag.Bool("list-templates", false, "List available templates and exit")
	flag.Parse()

	configPath := projectPath("config.json")
	configData, err := os.ReadFile(configPath)
	if err != nil {
		log.Fatalf("Error reading config.json at %s: %v", configPath, err)
	}

	var config Config
	if err := json.Unmarshal(configData, &config); err != nil {
		log.Fatalf("Error parsing config.json: %v", err)
	}

	templates := getAvailableTemplates()
	defaultTemplate := config.Template
	if defaultTemplate == "" {
		defaultTemplate = "modern"
	}

	if *listTemplates {
		for i, t := range templates {
			fmt.Printf("%d|%s\n", i+1, t)
		}
		return
	}

	if *interactive {
		runInteractive(&config, templates, defaultTemplate, createCsv)
		return
	}

	if *splitSize > 0 {
		config.SplitSize = *splitSize
	}
	if *templateName != "" {
		resolved := *templateName
		if idx, err := strconv.Atoi(resolved); err == nil && idx >= 1 && idx <= len(templates) {
			resolved = templates[idx-1]
		}
		tplPath := projectPath("templates", resolved+".gohtml")
		if _, err := os.Stat(tplPath); err == nil {
			config.Template = resolved
		} else {
			fmt.Printf("Warning: Template '%s' not found, using config default.\n", resolved)
		}
	}
	if *workers > 0 {
		config.WorkerCount = *workers
	}
	if config.WorkerCount <= 0 {
		config.WorkerCount = 1
	}

	fmt.Printf("Split size  : %d\n", config.SplitSize)
	fmt.Printf("Template    : %s\n", config.Template)
	fmt.Printf("Workers     : %d\n", config.WorkerCount)
	fmt.Printf("Create CSV  : %s\n", *createCsv)
	fmt.Println(strings.Repeat("-", 40))

	runExport(config, *createCsv)
}

func runInteractive(config *Config, templates []string, defaultTemplate string, createCsv *string) {
	reader := bufio.NewReader(os.Stdin)

	fmt.Println(strings.Repeat("=", 40))
	fmt.Println("   Export Snippet Articles (Go)")
	fmt.Println(strings.Repeat("=", 40))
	fmt.Println()

	defaultSplit := config.SplitSize
	if defaultSplit <= 0 {
		defaultSplit = 5000
	}

	fmt.Printf("Split size [%d]: ", defaultSplit)
	splitInput, _ := reader.ReadString('\n')
	splitInput = strings.TrimSpace(splitInput)
	if splitInput != "" {
		if v, err := strconv.Atoi(splitInput); err == nil && v > 0 {
			config.SplitSize = v
		}
	}
	if config.SplitSize <= 0 {
		config.SplitSize = defaultSplit
	}

	fmt.Println()
	fmt.Println("Available templates:")
	for i, t := range templates {
		marker := ""
		if t == defaultTemplate {
			marker = " (current)"
		}
		fmt.Printf("  %2d. %s%s\n", i+1, t, marker)
	}
	fmt.Println()

	fmt.Printf("Template number [1-%d] (default: %s): ", len(templates), defaultTemplate)
	tplInput, _ := reader.ReadString('\n')
	tplInput = strings.TrimSpace(tplInput)
	if tplInput != "" {
		if idx, err := strconv.Atoi(tplInput); err == nil && idx >= 1 && idx <= len(templates) {
			config.Template = templates[idx-1]
		} else {
			found := false
			for _, t := range templates {
				if t == tplInput {
					config.Template = t
					found = true
					break
				}
			}
			if !found {
				config.Template = defaultTemplate
			}
		}
	} else {
		config.Template = defaultTemplate
	}

	fmt.Println()
	defaultWorkers := config.WorkerCount
	if defaultWorkers <= 0 {
		defaultWorkers = 1
	}
	fmt.Printf("Workers [%d]: ", defaultWorkers)
	workersInput, _ := reader.ReadString('\n')
	workersInput = strings.TrimSpace(workersInput)
	if workersInput != "" {
		if v, err := strconv.Atoi(workersInput); err == nil && v > 0 {
			config.WorkerCount = v
		}
	}
	if config.WorkerCount <= 0 {
		config.WorkerCount = defaultWorkers
	}

	fmt.Println()
	fmt.Printf("Split size : %d\n", config.SplitSize)
	fmt.Printf("Template   : %s\n", config.Template)
	fmt.Printf("Workers    : %d\n", config.WorkerCount)
	fmt.Println()

	fmt.Print("Proceed with export? [Y/n]: ")
	confirm, _ := reader.ReadString('\n')
	confirm = strings.TrimSpace(strings.ToLower(confirm))
	if confirm == "n" {
		fmt.Println("Export cancelled.")
		return
	}

	fmt.Println()
	fmt.Println("Starting export...")
	fmt.Println()

	runExport(*config, *createCsv)
}

func runExport(config Config, createCsv string) {
	mappings, err := readCreateCsv(createCsv)
	if err != nil {
		log.Fatalf("Error reading CREATE.csv: %v", err)
	}
	fmt.Printf("Found %d bucket mappings in %s\n", len(mappings), createCsv)

	fmt.Println("Streaming snippet articles from database...")

	articleChan := make(chan SnippetArticle, config.Memory.BatchSize)
	errChan := make(chan error, 1)

	go streamSnippetArticles(config.DataFolder, config.Memory.BatchSize, articleChan, errChan)

	splitSizeVal := config.SplitSize
	if splitSizeVal <= 0 {
		splitSizeVal = 5000
	}

	chunkArticles := make([]SnippetArticle, 0, splitSizeVal)
	totalArticles := 0
	mappingIdx := 0

	for article := range articleChan {
		totalArticles++
		chunkArticles = append(chunkArticles, article)

		if len(chunkArticles) >= splitSizeVal && mappingIdx < len(mappings) {
			mapping := mappings[mappingIdx]
			fmt.Printf("\n[%s] Bucket: %s | Name: %s | Articles: %d\n", mapping.Foldering, mapping.Bucket, mapping.Name, len(chunkArticles))
			exporter := NewExporter(config, mapping.Bucket, mapping.Foldering, mapping.Name)
			exporter.Run(chunkArticles)
			chunkArticles = make([]SnippetArticle, 0, splitSizeVal)
			mappingIdx++
			runtime.GC()
		}
	}

	if err := <-errChan; err != nil {
		log.Fatalf("Error streaming articles: %v", err)
	}

	if len(chunkArticles) > 0 && mappingIdx < len(mappings) {
		mapping := mappings[mappingIdx]
		fmt.Printf("\n[%s] Bucket: %s | Name: %s | Articles: %d\n", mapping.Foldering, mapping.Bucket, mapping.Name, len(chunkArticles))
		exporter := NewExporter(config, mapping.Bucket, mapping.Foldering, mapping.Name)
		exporter.Run(chunkArticles)
		chunkArticles = nil
		mappingIdx++
	}

	for ; mappingIdx < len(mappings); mappingIdx++ {
		fmt.Printf("\n[%s] SKIPPED - no articles left\n", mappings[mappingIdx].Foldering)
	}

	fmt.Printf("\nTotal articles streamed: %d\n", totalArticles)
	fmt.Println(strings.Repeat("=", 40))
	fmt.Println("All exports completed!")
}

func getAvailableTemplates() []string {
	templateDir := projectPath("templates")
	entries, err := os.ReadDir(templateDir)
	if err != nil {
		return nil
	}

	var templates []string
	seen := make(map[string]bool)
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		ext := filepath.Ext(name)
		if ext == ".gohtml" {
			base := strings.TrimSuffix(name, ext)
			if base != "index" && base != "article" && base != "layout" {
				if !seen[base] {
					templates = append(templates, base)
					seen[base] = true
				}
			}
		}
	}
	sort.Strings(templates)
	return templates
}

func readCreateCsv(file string) ([]Mapping, error) {
	csvPath := projectPath(file)
	f, err := os.Open(csvPath)
	if err != nil {
		return nil, fmt.Errorf("cannot open %s: %w", csvPath, err)
	}
	defer f.Close()

	reader := csv.NewReader(f)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, fmt.Errorf("error reading CSV: %w", err)
	}

	if len(records) < 2 {
		return nil, fmt.Errorf("CSV has no data rows")
	}

	headers := records[0]
	bucketIdx, folderingIdx, nameIdx := -1, -1, -1
	for i, h := range headers {
		switch strings.TrimSpace(strings.ToLower(h)) {
		case "bucket":
			bucketIdx = i
		case "foldering":
			folderingIdx = i
		case "name":
			nameIdx = i
		}
	}

	if bucketIdx == -1 || folderingIdx == -1 || nameIdx == -1 {
		return nil, fmt.Errorf("CSV missing required columns (bucket, foldering, name)")
	}

	var mappings []Mapping
	for _, row := range records[1:] {
		if len(row) > max3(bucketIdx, folderingIdx, nameIdx) {
			mappings = append(mappings, Mapping{
				Bucket:    strings.TrimSpace(row[bucketIdx]),
				Foldering: strings.TrimSpace(row[folderingIdx]),
				Name:      strings.TrimSpace(row[nameIdx]),
			})
		}
	}

	return mappings, nil
}

func max3(a, b, c int) int {
	if a > b {
		if a > c {
			return a
		}
		return c
	}
	if b > c {
		return b
	}
	return c
}
