package storage

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

const (
	ProviderFilesystem      = "filesystem"
	ProviderS3              = "s3"
	DefaultFilesystemBucket = "local-filesystem"
	DefaultFilesystemRoot   = "data/object-storage"
	DefaultLegacyUploadRoot = "data/uploads"
)

type Config struct {
	Provider         string
	FilesystemRoot   string
	LegacyUploadRoot string
	Bucket           string
	Region           string
	Endpoint         string
	AccessKeyID      string
	SecretAccessKey  string
	SessionToken     string
	KeyPrefix        string
	UsePathStyle     bool
}

func LoadConfigFromEnv() Config {
	provider := strings.ToLower(strings.TrimSpace(os.Getenv("FILE_STORAGE_PROVIDER")))
	if provider == "" {
		if hasS3RuntimeHints() {
			provider = ProviderS3
		} else {
			provider = ProviderFilesystem
		}
	}

	root := strings.TrimSpace(os.Getenv("FILE_STORAGE_ROOT"))
	if root == "" {
		root = DefaultFilesystemRoot
	}
	root = resolveRuntimePath(root)

	legacyUploadRoot := strings.TrimSpace(os.Getenv("FILE_STORAGE_LEGACY_UPLOAD_ROOT"))
	if legacyUploadRoot == "" {
		legacyUploadRoot = DefaultLegacyUploadRoot
	}
	legacyUploadRoot = resolveRuntimePath(legacyUploadRoot)

	bucket := strings.TrimSpace(os.Getenv("FILE_STORAGE_BUCKET"))
	if provider == ProviderFilesystem && bucket == "" {
		bucket = DefaultFilesystemBucket
	}

	return Config{
		Provider:         provider,
		FilesystemRoot:   root,
		LegacyUploadRoot: legacyUploadRoot,
		Bucket:           bucket,
		Region:           strings.TrimSpace(os.Getenv("FILE_STORAGE_REGION")),
		Endpoint:         strings.TrimSpace(os.Getenv("FILE_STORAGE_ENDPOINT")),
		AccessKeyID:      strings.TrimSpace(os.Getenv("FILE_STORAGE_ACCESS_KEY_ID")),
		SecretAccessKey:  strings.TrimSpace(os.Getenv("FILE_STORAGE_SECRET_ACCESS_KEY")),
		SessionToken:     strings.TrimSpace(os.Getenv("FILE_STORAGE_SESSION_TOKEN")),
		KeyPrefix:        strings.TrimSpace(os.Getenv("FILE_STORAGE_PREFIX")),
		UsePathStyle:     parseEnvBool("FILE_STORAGE_USE_PATH_STYLE"),
	}
}

func NewStoreFromEnv() (ObjectStore, error) {
	return NewStore(LoadConfigFromEnv())
}

// NewRuntimeFilesystemStore constructs a filesystem store whose relative root
// follows the same runtime/source-root anchoring contract as FILE_STORAGE_ROOT.
// Callers with a dedicated filesystem namespace can therefore avoid resolving
// paths from an arbitrary process working directory.
func NewRuntimeFilesystemStore(root string) (*FilesystemStore, error) {
	return NewFilesystemStore(resolveRuntimePath(root))
}

func NewStore(cfg Config) (ObjectStore, error) {
	switch cfg.Provider {
	case ProviderFilesystem:
		return NewFilesystemStore(cfg.FilesystemRoot)
	case ProviderS3:
		return NewS3Store(cfg)
	default:
		return nil, fmt.Errorf("unsupported file storage provider %q", cfg.Provider)
	}
}

func parseEnvBool(name string) bool {
	value := strings.ToLower(strings.TrimSpace(os.Getenv(name)))
	switch value {
	case "1", "true", "yes", "on":
		return true
	default:
		return false
	}
}

func hasS3RuntimeHints() bool {
	for _, name := range []string{
		"FILE_STORAGE_BUCKET",
		"FILE_STORAGE_ENDPOINT",
		"FILE_STORAGE_ACCESS_KEY_ID",
		"FILE_STORAGE_SECRET_ACCESS_KEY",
		"AWS_ACCESS_KEY_ID",
		"AWS_SECRET_ACCESS_KEY",
		"AWS_PROFILE",
	} {
		if strings.TrimSpace(os.Getenv(name)) != "" {
			return true
		}
	}
	return false
}

func resolveRuntimePath(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ""
	}
	if filepath.IsAbs(raw) {
		return filepath.Clean(raw)
	}
	return filepath.Join(runtimeBaseDir(), raw)
}

func runtimeBaseDir() string {
	if configured := strings.TrimSpace(os.Getenv("NEXUSCRM_RUNTIME_ROOT")); configured != "" {
		return absOrClean(configured)
	}
	cwd, err := os.Getwd()
	if err != nil {
		return "."
	}
	if root, ok := findSourceRepoRoot(cwd); ok {
		return root
	}
	return cwd
}

func findSourceRepoRoot(start string) (string, bool) {
	dir := absOrClean(start)
	for {
		if fileExists(filepath.Join(dir, "go.work")) && fileExists(filepath.Join(dir, "backend", "go.mod")) {
			return dir, true
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return "", false
		}
		dir = parent
	}
}

func absOrClean(path string) string {
	abs, err := filepath.Abs(path)
	if err != nil {
		return filepath.Clean(path)
	}
	return abs
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}
