package config

import (
	"fmt"
	"log/slog"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/joho/godotenv"
)

type Config struct {
	Server          ServerConfig
	Database        DatabaseConfig
	Auth            AuthConfig
	OAuth           OAuthConfig
	App             AppConfig
	Email           EmailConfig
	I18n            I18n
	Pdf             PdfPrinter
	GoogleTranslate GoogleTranslateConfig
	Image           ImageConfig
	Cors            CorsConfig
	MailiSearch     MeiliSearchConfig
}

type I18n struct {
	Langs []string `env:"I18N_LANGS,en,pl"`
}
type ServerConfig struct {
	Port int    `env:"SERVER_PORT,3000"`
	Host string `env:"SERVER_HOST,0.0.0.0"`
}

type CorsConfig struct {
	Origins []string `env:"CORS_ORGIN"`
}

type MeiliSearchConfig struct {
	ServerURL string `env:"MEILISEARCH_URL"`
	ApiKey    string `env:"MEILISEARCH_API_KEY"`
}

type ImageConfig struct {
	ImagePrefix string `env:"IMAGE_PREFIX"`
}

type DatabaseConfig struct {
	Host            string        `env:"DB_HOST,localhost"`
	Port            int           `env:"DB_PORT"`
	User            string        `env:"DB_USER"`
	Password        string        `env:"DB_PASSWORD"`
	Name            string        `env:"DB_NAME"`
	SSLMode         string        `env:",disable"`
	MaxIdleConns    int           `env:",10"`
	MaxOpenConns    int           `env:",100"`
	ConnMaxLifetime time.Duration `env:",1h"`
}

type AuthConfig struct {
	JWTSecret         string `env:"AUTH_JWT_SECRET"`
	JWTExpiration     int    `env:"AUTH_JWT_EXPIRATION"`
	RefreshExpiration int    `env:"AUTH_REFRESH_EXPIRATION"`
}

type OAuthConfig struct {
	Google GoogleOAuthConfig
}

type GoogleOAuthConfig struct {
	ClientID     string   `env:"OAUTH_GOOGLE_CLIENT_ID"`
	ClientSecret string   `env:"OAUTH_GOOGLE_CLIENT_SECRET"`
	RedirectURL  string   `env:"OAUTH_GOOGLE_REDIRECT_URL"`
	Scopes       []string `env:""`
}

type AppConfig struct {
	Name        string `env:"APP_NAME,Gitea Manager"`
	Version     string `env:"APP_VERSION,1.0.0"`
	Environment string `env:"APP_ENVIRONMENT,development"`
	BaseURL     string `env:"APP_BASE_URL,http://localhost:5173"`
}

type EmailConfig struct {
	SMTPHost     string `env:"EMAIL_SMTP_HOST,localhost"`
	SMTPPort     int    `env:"EMAIL_SMTP_PORT,587"`
	SMTPUser     string `env:"EMAIL_SMTP_USER"`
	SMTPPassword string `env:"EMAIL_SMTP_PASSWORD"`
	FromEmail    string `env:"EMAIL_FROM,noreply@example.com"`
	FromName     string `env:"EMAIL_FROM_NAME,Gitea Manager"`
	AdminEmail   string `env:"EMAIL_ADMIN,admin@example.com"`
	Enabled      bool   `env:"EMAIL_ENABLED,false"`
}

type PdfPrinter struct {
	ServerUrl string `env:"PDF_SERVER_URL,http://localhost:8000"`
}

// GoogleTranslateConfig holds configuration for the Google Cloud Translation API.
// CredentialsFile should point to a service account JSON key file.
// ProjectID is your Google Cloud project ID (e.g. "my-project-123").
type GoogleTranslateConfig struct {
	CredentialsFile string `env:"GOOGLE_APPLICATION_CREDENTIALS"`
	ProjectID       string `env:"GOOGLE_CLOUD_PROJECT_ID"`
}

var cfg *Config

func init() {
	if cfg == nil {
		cfg = load()
	}
}

func Get() *Config {
	return cfg
}

// GetDSN returns the database connection string
func (c *DatabaseConfig) GetDSN() string {
	return fmt.Sprintf(
		"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		c.User,
		c.Password,
		c.Host,
		c.Port,
		c.Name,
	)
}
func load() *Config {

	cfg := &Config{}

	err := godotenv.Load(".env")
	if err != nil {
		slog.Error("not possible to load env: %s", err.Error(), "")
	}
	err = loadEnv(&cfg.Database)
	if err != nil {
		slog.Error("not possible to load env variables for database : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Server)
	if err != nil {
		slog.Error("not possible to load env variables for server : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Auth)
	if err != nil {
		slog.Error("not possible to load env variables for auth : ", err.Error(), "")
	}

	err = loadEnv(&cfg.OAuth.Google)
	if err != nil {
		slog.Error("not possible to load env variables for outh google : ", err.Error(), "")
	}

	err = loadEnv(&cfg.App)
	if err != nil {
		slog.Error("not possible to load env variables for app : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Email)
	if err != nil {
		slog.Error("not possible to load env variables for email : ", err.Error(), "")
	}

	err = loadEnv(&cfg.I18n)
	if err != nil {
		slog.Error("not possible to load env variables for email : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Pdf)
	if err != nil {
		slog.Error("not possible to load env variables for email : ", err.Error(), "")
	}

	err = loadEnv(&cfg.GoogleTranslate)
	if err != nil {
		slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Image)
	if err != nil {
		slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
	}

	err = loadEnv(&cfg.Cors)
	if err != nil {
		slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
	}

	err = loadEnv(&cfg.MailiSearch)
	if err != nil {
		slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
	}

	return cfg
}

func loadEnv(dst any) error {
	v := reflect.ValueOf(dst)
	if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("dst must be pointer to struct")
	}

	return loadStruct(v.Elem())
}

func loadStruct(v reflect.Value) error {
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		fieldType := t.Field(i)

		if !field.CanSet() {
			continue
		}

		// nested struct
		if field.Kind() == reflect.Struct && field.Type() != reflect.TypeOf(time.Duration(0)) {
			if err := loadStruct(field); err != nil {
				return err
			}
			continue
		}

		tag := fieldType.Tag.Get("env")
		key, def := parseEnvTag(tag)

		if key == "" {
			key = fieldType.Name
		}

		val, ok := os.LookupEnv(key)

		// fallback to default
		if !ok && def != nil {
			val = *def
			ok = true
		}

		if !ok {
			continue
		}

		if err := setValue(field, val, key); err != nil {
			return err
		}
	}

	return nil
}

func setValue(field reflect.Value, val string, key string) error {
	switch field.Kind() {
	case reflect.String:
		field.SetString(val)

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		// time.Duration
		if field.Type() == reflect.TypeOf(time.Duration(0)) {
			d, err := time.ParseDuration(val)
			if err != nil {
				return fmt.Errorf("env %s: %w", key, err)
			}
			field.SetInt(int64(d))
			return nil
		}

		i, err := strconv.Atoi(val)
		if err != nil {
			return fmt.Errorf("env %s: %w", key, err)
		}
		field.SetInt(int64(i))

	case reflect.Bool:
		b, err := strconv.ParseBool(val)
		if err != nil {
			return fmt.Errorf("env %s: %w", key, err)
		}
		field.SetBool(b)

	case reflect.Slice:
		if field.Type().Elem().Kind() == reflect.String {
			// Split by comma and trim whitespace
			parts := strings.Split(val, ",")
			slice := make([]string, 0, len(parts))
			for _, p := range parts {
				p = strings.TrimSpace(p)
				if p != "" {
					slice = append(slice, p)
				}
			}
			field.Set(reflect.ValueOf(slice))
		} else {
			return fmt.Errorf("unsupported slice type %s for env %s", field.Type().Elem().Kind(), key)
		}

	default:
		return fmt.Errorf("unsupported type %s for env %s", field.Kind(), key)
	}

	return nil
}

func parseEnvTag(tag string) (key string, def *string) {
	if tag == "" {
		return "", nil
	}

	parts := strings.SplitN(tag, ",", 2)
	key = parts[0]

	if len(parts) == 2 {
		return key, &parts[1] // Returns "en,pl,de" for slices - setValue handles the split
	}

	return key, nil
}
