package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	IncludeBNodes bool

	CorsOrigins string

	DefaultNodeLimit int
	DefaultEdgeLimit int
	MaxNodeLimit     int
	MaxEdgeLimit     int

	EdgeBatchSize int

	FreeOSMemoryAfterSnapshot bool
	LogSnapshotTimings        bool

	SparqlHost         string
	SparqlEndpoint     string
	SparqlUser         string
	SparqlPass         string
	SparqlInsecureTLS  bool
	SparqlDataFile     string
	SparqlGraphIRI     string
	SparqlLoadOnStart  bool
	SparqlClearOnStart bool

	SparqlTimeout      time.Duration
	SparqlReadyRetries int
	SparqlReadyDelay   time.Duration
	SparqlReadyTimeout time.Duration

	ListenAddr string
}

func LoadConfig() (Config, error) {
	cfg := Config{
		IncludeBNodes: envBool("INCLUDE_BNODES", false),
		CorsOrigins:   envString("CORS_ORIGINS", "*"),

		DefaultNodeLimit: envInt("DEFAULT_NODE_LIMIT", 800_000),
		DefaultEdgeLimit: envInt("DEFAULT_EDGE_LIMIT", 2_000_000),
		MaxNodeLimit:     envInt("MAX_NODE_LIMIT", 10_000_000),
		MaxEdgeLimit:     envInt("MAX_EDGE_LIMIT", 20_000_000),
		EdgeBatchSize:    envInt("EDGE_BATCH_SIZE", 100_000),
		FreeOSMemoryAfterSnapshot: envBool("FREE_OS_MEMORY_AFTER_SNAPSHOT", false),
		LogSnapshotTimings:        envBool("LOG_SNAPSHOT_TIMINGS", false),

		SparqlHost:         envString("SPARQL_HOST", "http://anzograph:8080"),
		SparqlEndpoint:     envString("SPARQL_ENDPOINT", ""),
		SparqlUser:         envString("SPARQL_USER", ""),
		SparqlPass:         envString("SPARQL_PASS", ""),
		SparqlInsecureTLS:  envBool("SPARQL_INSECURE_TLS", false),
		SparqlDataFile:     envString("SPARQL_DATA_FILE", ""),
		SparqlGraphIRI:     envString("SPARQL_GRAPH_IRI", ""),
		SparqlLoadOnStart:  envBool("SPARQL_LOAD_ON_START", false),
		SparqlClearOnStart: envBool("SPARQL_CLEAR_ON_START", false),

		SparqlReadyRetries: envInt("SPARQL_READY_RETRIES", 30),
		ListenAddr:         envString("LISTEN_ADDR", ":8000"),
	}

	var err error
	cfg.SparqlTimeout, err = envSeconds("SPARQL_TIMEOUT_S", 300)
	if err != nil {
		return Config{}, err
	}
	cfg.SparqlReadyDelay, err = envSeconds("SPARQL_READY_DELAY_S", 4)
	if err != nil {
		return Config{}, err
	}
	cfg.SparqlReadyTimeout, err = envSeconds("SPARQL_READY_TIMEOUT_S", 10)
	if err != nil {
		return Config{}, err
	}

	if cfg.SparqlLoadOnStart && strings.TrimSpace(cfg.SparqlDataFile) == "" {
		return Config{}, fmt.Errorf("SPARQL_LOAD_ON_START=true but SPARQL_DATA_FILE is not set")
	}

	if cfg.DefaultNodeLimit < 1 {
		return Config{}, fmt.Errorf("DEFAULT_NODE_LIMIT must be >= 1")
	}
	if cfg.DefaultEdgeLimit < 1 {
		return Config{}, fmt.Errorf("DEFAULT_EDGE_LIMIT must be >= 1")
	}
	if cfg.MaxNodeLimit < 1 {
		return Config{}, fmt.Errorf("MAX_NODE_LIMIT must be >= 1")
	}
	if cfg.MaxEdgeLimit < 1 {
		return Config{}, fmt.Errorf("MAX_EDGE_LIMIT must be >= 1")
	}
	if cfg.DefaultNodeLimit > cfg.MaxNodeLimit {
		return Config{}, fmt.Errorf("DEFAULT_NODE_LIMIT must be <= MAX_NODE_LIMIT")
	}
	if cfg.DefaultEdgeLimit > cfg.MaxEdgeLimit {
		return Config{}, fmt.Errorf("DEFAULT_EDGE_LIMIT must be <= MAX_EDGE_LIMIT")
	}
	if cfg.EdgeBatchSize < 1 {
		return Config{}, fmt.Errorf("EDGE_BATCH_SIZE must be >= 1")
	}
	if cfg.EdgeBatchSize > cfg.MaxEdgeLimit {
		return Config{}, fmt.Errorf("EDGE_BATCH_SIZE must be <= MAX_EDGE_LIMIT")
	}

	return cfg, nil
}

func (c Config) EffectiveSparqlEndpoint() string {
	if strings.TrimSpace(c.SparqlEndpoint) != "" {
		return strings.TrimSpace(c.SparqlEndpoint)
	}
	return strings.TrimRight(c.SparqlHost, "/") + "/sparql"
}

func (c Config) corsOriginList() []string {
	raw := strings.TrimSpace(c.CorsOrigins)
	if raw == "" || raw == "*" {
		return []string{"*"}
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		out = append(out, p)
	}
	if len(out) == 0 {
		return []string{"*"}
	}
	return out
}

func envString(name, def string) string {
	v := os.Getenv(name)
	if strings.TrimSpace(v) == "" {
		return def
	}
	return v
}

func envBool(name string, def bool) bool {
	v := strings.TrimSpace(os.Getenv(name))
	if v == "" {
		return def
	}
	switch strings.ToLower(v) {
	case "1", "true", "yes", "y", "on":
		return true
	case "0", "false", "no", "n", "off":
		return false
	default:
		return def
	}
}

func envInt(name string, def int) int {
	v := strings.TrimSpace(os.Getenv(name))
	if v == "" {
		return def
	}
	v = strings.ReplaceAll(v, "_", "")
	n, err := strconv.Atoi(v)
	if err != nil {
		return def
	}
	return n
}

func envSeconds(name string, def float64) (time.Duration, error) {
	v := strings.TrimSpace(os.Getenv(name))
	if v == "" {
		return time.Duration(def * float64(time.Second)), nil
	}
	f, err := strconv.ParseFloat(v, 64)
	if err != nil {
		return 0, fmt.Errorf("%s must be a number (seconds): %w", name, err)
	}
	return time.Duration(f * float64(time.Second)), nil
}
