package admin

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

	"gopkg.in/yaml.v3"
)

const defaultAdminAddr = ":8080"
const defaultMigrationsDir = "migrations"

type Config struct {
	Addr          string
	DatabaseURL   string
	MigrationsDir string
	RepoRoot      string
	PostsDir      string
	SiteDir       string
	StaticDir     string
	AdminDir      string
	LogFile       string
	LogMaxBytes   int64
	LogMaxBackups int
	DeepSeek      DeepSeekConfig
	LocalLLM      LocalLLMConfig
	R2            R2Config
	SlugProvider  string
}

type DeepSeekConfig struct {
	APIKey  string
	BaseURL string
	Model   string
}

type LocalLLMConfig struct {
	URL         string
	Model       string
	Temperature float64
	TopP        float64
	NumPredict  int
}

type R2Config struct {
	Endpoint        string
	Bucket          string
	Prefix          string
	AccessKeyID     string
	SecretAccessKey string
	Region          string
	PublicBaseURL   string
	MaxUploadBytes  int64
}

type localConfig struct {
	Database struct {
		PostgresDSN string `yaml:"postgres_dsn"`
	} `yaml:"database"`
	Slug struct {
		Provider string `yaml:"provider"`
	} `yaml:"slug"`
	DeepSeek struct {
		APIKey    string `yaml:"api_key"`
		APIKeyEnv string `yaml:"api_key_env"`
		BaseURL   string `yaml:"base_url"`
		Model     string `yaml:"model"`
	} `yaml:"deepseek"`
	LocalLLM struct {
		URL         string  `yaml:"url"`
		Model       string  `yaml:"model"`
		Temperature float64 `yaml:"temperature"`
		TopP        float64 `yaml:"top_p"`
		NumPredict  int     `yaml:"num_predict"`
	} `yaml:"local_llm"`
	R2 struct {
		Endpoint        string `yaml:"endpoint"`
		Bucket          string `yaml:"bucket"`
		Prefix          string `yaml:"prefix"`
		AccessKeyID     string `yaml:"accessKeyId"`
		SecretAccessKey string `yaml:"secretAccessKey"`
		Region          string `yaml:"region"`
		PublicBaseURL   string `yaml:"publicBaseUrl"`
		MaxUploadBytes  int64  `yaml:"maxUploadBytes"`
	} `yaml:"r2"`
}

func LoadConfig() Config {
	addr := os.Getenv("OSAET_ADMIN_ADDR")
	if addr == "" {
		addr = defaultAdminAddr
	}

	migrationsDir := os.Getenv("OSAET_MIGRATIONS_DIR")
	if migrationsDir == "" {
		migrationsDir = defaultMigrationsDir
	}

	repoRoot := os.Getenv("OSAET_REPO_ROOT")
	if repoRoot == "" {
		repoRoot = ".."
	}

	postsDir := os.Getenv("OSAET_POSTS_DIR")
	if postsDir == "" {
		postsDir = filepath.Join(repoRoot, "content", "posts")
	}

	siteDir := os.Getenv("OSAET_SITE_DIR")
	if siteDir == "" {
		siteDir = filepath.Join(repoRoot, "frontend", "site")
	}

	staticDir := os.Getenv("OSAET_STATIC_DIR")
	if staticDir == "" {
		staticDir = filepath.Join(repoRoot, "dist", "site")
	}

	adminDir := os.Getenv("OSAET_ADMIN_DIR")
	if adminDir == "" {
		adminDir = filepath.Join(repoRoot, "frontend", "admin", "dist", "admin", "browser")
	}

	logFile := os.Getenv("OSAET_LOG_FILE")
	if logFile == "" {
		logFile = filepath.Join(repoRoot, ".osaet", "logs", "osaet-admin.log")
	}

	local := loadLocalConfig(repoRoot)
	databaseURL := firstNonEmpty(os.Getenv("DATABASE_URL"), local.Database.PostgresDSN)

	deepSeekAPIKeyEnv := firstNonEmpty(local.DeepSeek.APIKeyEnv, "DEEPSEEK_API_KEY")
	deepSeekAPIKey := strings.TrimSpace(os.Getenv(deepSeekAPIKeyEnv))
	if deepSeekAPIKey == "" && deepSeekAPIKeyEnv != "DEEPSEEK_API_KEY" {
		deepSeekAPIKey = strings.TrimSpace(os.Getenv("DEEPSEEK_API_KEY"))
	}
	if deepSeekAPIKey == "" {
		deepSeekAPIKey = strings.TrimSpace(local.DeepSeek.APIKey)
	}

	return Config{
		Addr:          addr,
		DatabaseURL:   databaseURL,
		MigrationsDir: migrationsDir,
		RepoRoot:      repoRoot,
		PostsDir:      postsDir,
		SiteDir:       siteDir,
		StaticDir:     staticDir,
		AdminDir:      adminDir,
		LogFile:       logFile,
		LogMaxBytes:   int64(firstNonZeroInt(envInt("OSAET_LOG_MAX_BYTES"), 10*1024*1024)),
		LogMaxBackups: firstNonZeroInt(envInt("OSAET_LOG_MAX_BACKUPS"), 5),
		SlugProvider:  firstNonEmpty(os.Getenv("OSAET_SLUG_PROVIDER"), local.Slug.Provider, "deepseek"),
		DeepSeek: DeepSeekConfig{
			APIKey:  deepSeekAPIKey,
			BaseURL: firstNonEmpty(os.Getenv("DEEPSEEK_BASE_URL"), local.DeepSeek.BaseURL),
			Model:   firstNonEmpty(os.Getenv("DEEPSEEK_MODEL"), local.DeepSeek.Model),
		},
		LocalLLM: LocalLLMConfig{
			URL:         firstNonEmpty(os.Getenv("LOCAL_LLM_URL"), local.LocalLLM.URL, "http://127.0.0.1:11434/api/generate"),
			Model:       firstNonEmpty(os.Getenv("LOCAL_LLM_MODEL"), local.LocalLLM.Model),
			Temperature: firstNonZeroFloat(envFloat("LOCAL_LLM_TEMPERATURE"), local.LocalLLM.Temperature, 0.1),
			TopP:        firstNonZeroFloat(envFloat("LOCAL_LLM_TOP_P"), local.LocalLLM.TopP, 0.8),
			NumPredict:  firstNonZeroInt(envInt("LOCAL_LLM_NUM_PREDICT"), local.LocalLLM.NumPredict, 32),
		},
		R2: R2Config{
			Endpoint:        firstNonEmpty(os.Getenv("OSAET_R2_ENDPOINT"), local.R2.Endpoint),
			Bucket:          firstNonEmpty(os.Getenv("OSAET_R2_BUCKET"), local.R2.Bucket),
			Prefix:          firstNonEmpty(os.Getenv("OSAET_R2_PREFIX"), local.R2.Prefix),
			AccessKeyID:     firstNonEmpty(os.Getenv("OSAET_R2_ACCESS_KEY_ID"), local.R2.AccessKeyID),
			SecretAccessKey: firstNonEmpty(os.Getenv("OSAET_R2_SECRET_ACCESS_KEY"), local.R2.SecretAccessKey),
			Region:          firstNonEmpty(os.Getenv("OSAET_R2_REGION"), local.R2.Region, "auto"),
			PublicBaseURL:   firstNonEmpty(os.Getenv("OSAET_R2_PUBLIC_BASE_URL"), local.R2.PublicBaseURL),
			MaxUploadBytes:  int64(firstNonZeroInt(envInt("OSAET_R2_MAX_UPLOAD_BYTES"), int(local.R2.MaxUploadBytes), 20*1024*1024)),
		},
	}
}

func loadLocalConfig(repoRoot string) localConfig {
	var config localConfig
	paths := []string{}
	if path := strings.TrimSpace(os.Getenv("OSAET_LOCAL_CONFIG")); path != "" {
		paths = append(paths, path)
	} else {
		paths = append(paths,
			filepath.Join(repoRoot, "config", "local.yaml"),
			filepath.Join("config", "local.yaml"),
			filepath.Join("..", "config", "local.yaml"),
		)
	}

	path, data, ok := readFirstExistingFile(paths)
	if !ok {
		return config
	}
	if err := yaml.Unmarshal(data, &config); err != nil {
		fmt.Fprintf(os.Stderr, "warning: failed to read %s: %v\n", path, err)
	}
	return config
}

func readFirstExistingFile(paths []string) (string, []byte, bool) {
	for _, path := range paths {
		data, err := os.ReadFile(path)
		if err == nil {
			return path, data, true
		}
	}
	return "", nil, false
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func firstNonZeroFloat(values ...float64) float64 {
	for _, value := range values {
		if value != 0 {
			return value
		}
	}
	return 0
}

func firstNonZeroInt(values ...int) int {
	for _, value := range values {
		if value != 0 {
			return value
		}
	}
	return 0
}

func envFloat(key string) float64 {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return 0
	}
	var parsed float64
	if _, err := fmt.Sscanf(value, "%f", &parsed); err != nil {
		return 0
	}
	return parsed
}

func envInt(key string) int {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return 0
	}
	var parsed int
	if _, err := fmt.Sscanf(value, "%d", &parsed); err != nil {
		return 0
	}
	return parsed
}

func (c Config) Validate() error {
	if c.Addr == "" {
		return errors.New("admin addr is required")
	}
	if c.DatabaseURL == "" {
		return errors.New("DATABASE_URL or config/local.yaml database.postgres_dsn is required")
	}
	if c.MigrationsDir == "" {
		return errors.New("migrations dir is required")
	}
	if c.PostsDir == "" {
		return errors.New("posts dir is required")
	}
	if c.SiteDir == "" {
		return errors.New("site dir is required")
	}
	if c.StaticDir == "" {
		return errors.New("static dir is required")
	}
	return nil
}
