package config

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

type Config struct {
	App       App
	HTTP      HTTP
	DB        DB
	Redis     Redis
	NATS      NATS
	GCS       GCS
	JWT       JWT
	Google    Google
	IAMDelima IAMDelima
	Auth      Auth
	I18n      I18n
}

type App struct {
	Env string
}

func (a App) IsProduction() bool {
	return a.Env == "production"
}

func (a App) GinMode() string {
	if a.IsProduction() {
		return "release"
	}
	return "debug"
}

type HTTP struct {
	Addr string
}

type DB struct {
	Host     string
	Port     string
	Name     string
	User     string
	Password string
	Params   string
}

func (d DB) DSN() string {
	params := d.Params
	if params == "" {
		params = "charset=utf8mb4&parseTime=true&loc=Local"
	}
	return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", d.User, d.Password, d.Host, d.Port, d.Name, params)
}

type Redis struct {
	Addr     string
	Password string
	DB       int
}

type NATS struct {
	URL string
}

type GCS struct {
	Endpoint string
	Bucket   string
	KeyFile  string
	Insecure bool
}

type JWT struct {
	Secret     string
	Issuer     string
	AccessTTL  time.Duration
	RefreshTTL time.Duration
}

type Google struct {
	ClientID     string
	ClientSecret string
	RedirectURL  string
	Mock         bool
}

type IAMDelima struct {
	BaseURL string
	Token   string
	Mock    bool
}

type Auth struct {
	CookieName        string
	CookieDomain      string
	CookieSecure      bool
	PostLoginRedirect string
	Upstream          string
}

type I18n struct {
	DefaultLanguage string
}

func Load() (*Config, error) {
	loadDotEnv(".env")

	cfg := &Config{
		App: App{
			Env: env("APP_ENV", "development"),
		},
		HTTP: HTTP{
			Addr: env("HTTP_ADDR", ":8080"),
		},
		DB: DB{
			Host:     env("DB_HOST", "127.0.0.1"),
			Port:     env("DB_PORT", "3307"),
			Name:     env("DB_NAME", "ains"),
			User:     env("DB_USER", "dynaweb"),
			Password: env("DB_PASSWORD", ""),
			Params:   env("DB_PARAMS", "charset=utf8mb4&parseTime=true&loc=Local"),
		},
		Redis: Redis{
			Addr:     env("REDIS_ADDR", "127.0.0.1:6379"),
			Password: env("REDIS_PASSWORD", ""),
			DB:       envInt("REDIS_DB", 0),
		},
		NATS: NATS{
			URL: env("NATS_URL", "nats://127.0.0.1:4222"),
		},
		GCS: GCS{
			Endpoint: env("GCS_ENDPOINT", ""),
			Bucket:   env("GCS_BUCKET", ""),
			KeyFile:  env("GCS_KEY_FILE", ""),
			Insecure: envBool("GCS_INSECURE", false),
		},
		JWT: JWT{
			Secret:     env("JWT_SECRET", ""),
			Issuer:     env("JWT_ISSUER", "ains"),
			AccessTTL:  envDuration("JWT_ACCESS_TTL", 15*time.Minute),
			RefreshTTL: envDuration("JWT_REFRESH_TTL", 168*time.Hour),
		},
		Google: Google{
			ClientID:     env("GOOGLE_CLIENT_ID", ""),
			ClientSecret: env("GOOGLE_CLIENT_SECRET", ""),
			RedirectURL:  env("GOOGLE_REDIRECT_URL", ""),
			Mock:         envBool("GOOGLE_MOCK", false),
		},
		IAMDelima: IAMDelima{
			BaseURL: env("IAM_DELIMA_BASE_URL", ""),
			Token:   env("IAM_DELIMA_TOKEN", ""),
			Mock:    envBool("IAM_DELIMA_MOCK", false),
		},
		Auth: Auth{
			CookieName:        env("AUTH_COOKIE_NAME", "ains_token"),
			CookieDomain:      env("AUTH_COOKIE_DOMAIN", ""),
			CookieSecure:      envBool("AUTH_COOKIE_SECURE", false),
			PostLoginRedirect: env("AUTH_POST_LOGIN_REDIRECT", "/web/home"),
			Upstream:          env("AUTH_UPSTREAM", "http://127.0.0.1:8081"),
		},
		I18n: I18n{
			DefaultLanguage: env("DEFAULT_LANGUAGE", "ms"),
		},
	}

	return cfg, nil
}

func loadDotEnv(path string) {
	f, err := os.Open(path)
	if err != nil {
		return
	}
	defer func() { _ = f.Close() }()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		key, value, ok := strings.Cut(line, "=")
		if !ok {
			continue
		}
		key = strings.TrimSpace(key)
		value = strings.Trim(strings.TrimSpace(value), `"'`)
		if _, exists := os.LookupEnv(key); !exists {
			_ = os.Setenv(key, value)
		}
	}
}

func env(key, fallback string) string {
	if v, ok := os.LookupEnv(key); ok {
		return v
	}
	return fallback
}

func envInt(key string, fallback int) int {
	v, ok := os.LookupEnv(key)
	if !ok || v == "" {
		return fallback
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return fallback
	}
	return n
}

func envBool(key string, fallback bool) bool {
	v, ok := os.LookupEnv(key)
	if !ok || v == "" {
		return fallback
	}
	b, err := strconv.ParseBool(v)
	if err != nil {
		return fallback
	}
	return b
}

func envDuration(key string, fallback time.Duration) time.Duration {
	v, ok := os.LookupEnv(key)
	if !ok || v == "" {
		return fallback
	}
	d, err := time.ParseDuration(v)
	if err != nil {
		return fallback
	}
	return d
}
