// Package config loads photo-api configuration from the environment,
// following the backend's convention of a per-service .env file with
// ad-hoc defaults (backend/src/index.ts uses dotenv the same way).
package config

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

type Config struct {
	Port        int
	DBType      string // "postgres" | "sqlite", same convention as backend DB_TYPE
	DatabaseURL string
	// ViewTokenSecret signs the hour-bucketed gallery view tokens
	// (viewtoken.go). PHOTO_VIEW_SECRET, falling back to the legacy
	// JWT_SECRET during the Better Auth migration.
	ViewTokenSecret string

	// StorageBackend selects the active backend: "auto" (S3 when it is
	// configured, local otherwise), "local" or "s3". Explicit values let both
	// backends stay configured — required to run `photo-api sync`, and the
	// one-line switch after a sync.
	StorageBackend   string
	StoragePath      string
	S3Endpoint       string
	S3Region         string
	S3Bucket         string
	S3AccessKeyID    string
	S3SecretKey      string
	S3ForcePathStyle bool

	MaxUploadMB       int
	WorkerConcurrency int
	FrontendURL       string
	HeicConverter     string // optional explicit converter command; autodetected when empty
}

// S3Configured reports whether the S3 credentials are present at all,
// mirroring backend/src/lib/storage.ts: both S3_ENDPOINT and S3_BUCKET set.
// Kept separate from S3Enabled so a configured-but-inactive S3 backend can
// still be reached by `photo-api sync`.
func (c Config) S3Configured() bool {
	return c.S3Endpoint != "" && c.S3Bucket != ""
}

// S3Enabled reports whether S3 is the backend serving requests.
func (c Config) S3Enabled() bool {
	switch c.StorageBackend {
	case "s3":
		return true
	case "local":
		return false
	default: // auto
		return c.S3Configured()
	}
}

func Load() (Config, error) {
	loadDotenv(".env")

	cfg := Config{
		Port:              envInt("PORT", 3003),
		DBType:            strings.ToLower(env("DB_TYPE", "sqlite")),
		DatabaseURL:       env("DATABASE_URL", ""),
		ViewTokenSecret:   env("PHOTO_VIEW_SECRET", env("JWT_SECRET", "")),
		StorageBackend:    strings.ToLower(env("STORAGE_BACKEND", "auto")),
		StoragePath:       env("STORAGE_PATH", "./data/photos"),
		S3Endpoint:        env("S3_ENDPOINT", ""),
		S3Region:          env("S3_REGION", "auto"),
		S3Bucket:          env("S3_BUCKET", ""),
		S3AccessKeyID:     env("S3_ACCESS_KEY_ID", ""),
		S3SecretKey:       env("S3_SECRET_ACCESS_KEY", ""),
		S3ForcePathStyle:  envBool("S3_FORCE_PATH_STYLE", true),
		MaxUploadMB:       envInt("MAX_UPLOAD_MB", 50),
		WorkerConcurrency: envInt("WORKER_CONCURRENCY", 2),
		FrontendURL:       strings.TrimRight(env("FRONTEND_URL", "http://localhost:3000"), "/"),
		HeicConverter:     env("HEIC_CONVERTER", ""),
	}

	if cfg.DBType != "postgres" && cfg.DBType != "sqlite" {
		return cfg, fmt.Errorf("DB_TYPE must be postgres or sqlite, got %q", cfg.DBType)
	}
	if cfg.DatabaseURL == "" {
		return cfg, fmt.Errorf("DATABASE_URL is required")
	}
	if cfg.ViewTokenSecret == "" {
		return cfg, fmt.Errorf("PHOTO_VIEW_SECRET is required (or legacy JWT_SECRET as fallback)")
	}
	switch cfg.StorageBackend {
	case "auto", "local", "s3":
	default:
		return cfg, fmt.Errorf("STORAGE_BACKEND must be auto, local or s3, got %q", cfg.StorageBackend)
	}
	if cfg.StorageBackend == "s3" && !cfg.S3Configured() {
		return cfg, fmt.Errorf("STORAGE_BACKEND=s3 requires S3_ENDPOINT and S3_BUCKET")
	}
	return cfg, nil
}

func env(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func envInt(key string, def int) int {
	if v := os.Getenv(key); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			return n
		}
	}
	return def
}

func envBool(key string, def bool) bool {
	if v := os.Getenv(key); v != "" {
		if b, err := strconv.ParseBool(v); err == nil {
			return b
		}
	}
	return def
}

// loadDotenv is a minimal KEY=VALUE loader (comments and blank lines
// ignored, optional surrounding quotes stripped, existing env wins).
func loadDotenv(path string) {
	f, err := os.Open(path)
	if err != nil {
		return
	}
	defer f.Close()

	sc := bufio.NewScanner(f)
	for sc.Scan() {
		line := strings.TrimSpace(sc.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		key, val, ok := strings.Cut(line, "=")
		if !ok {
			continue
		}
		key = strings.TrimSpace(key)
		val = strings.TrimSpace(val)
		if len(val) >= 2 && (val[0] == '"' || val[0] == '\'') && val[len(val)-1] == val[0] {
			val = val[1 : len(val)-1]
		}
		if _, exists := os.LookupEnv(key); !exists {
			os.Setenv(key, val)
		}
	}
}
