package config

import (
	"bufio"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
)

type Config struct {
	AppAddr               string
	AppEnv                string
	AppDBDSN              string
	AssetManifestPath     string
	PrestaShopBaseURL     string
	PrestaShopProxyTarget string
	PrestaShopVersion     string
	PrestaShopCookieKey   string
	PrestaShopCookieIV    string
	PrestaShopCookieName  string
	PrestaShopDBDSN       string
	PrestaShopDBDialect   string
	PrestaShopTablePrefix string
	PrestaShopProjectRoot string
	PrestaShopBootstrap   string
	RouteOwnershipConfig  string
}

func Load() (Config, error) {
	if err := loadDotEnv(".env"); err != nil {
		return Config{}, err
	}

	cfg := Config{
		AppAddr:               envOr("APP_ADDR", ":8080"),
		AppEnv:                envOr("APP_ENV", "development"),
		AppDBDSN:              firstNonEmpty(os.Getenv("APP_DB_DSN"), os.Getenv("PRESTASHOP_DB_DSN"), dbDSNFromEnv()),
		AssetManifestPath:     envOr("ASSET_MANIFEST_PATH", "web/dist/manifest.json"),
		PrestaShopBaseURL:     os.Getenv("PRESTASHOP_BASE_URL"),
		PrestaShopProxyTarget: os.Getenv("PRESTASHOP_PROXY_TARGET"),
		PrestaShopVersion:     envOr("PRESTASHOP_VERSION", "1.7.3"),
		PrestaShopCookieKey:   os.Getenv("PRESTASHOP_COOKIE_KEY"),
		PrestaShopCookieIV:    os.Getenv("PRESTASHOP_COOKIE_IV"),
		PrestaShopCookieName:  os.Getenv("PRESTASHOP_COOKIE_NAME"),
		PrestaShopDBDSN:       firstNonEmpty(os.Getenv("PRESTASHOP_DB_DSN"), dbDSNFromEnv()),
		PrestaShopDBDialect:   envOr("PRESTASHOP_DB_DIALECT", "mariadb"),
		PrestaShopTablePrefix: firstNonEmpty(os.Getenv("PRESTASHOP_TABLE_PREFIX"), os.Getenv("DB_PREFIX"), "ps_"),
		PrestaShopProjectRoot: os.Getenv("PRESTASHOP_PROJECT_ROOT"),
		PrestaShopBootstrap:   os.Getenv("PRESTASHOP_BOOTSTRAP_PATH"),
		RouteOwnershipConfig:  envOr("ROUTE_OWNERSHIP_CONFIG", "/product/"),
	}

	if err := cfg.bootstrap(); err != nil {
		return Config{}, err
	}

	if cfg.PrestaShopProxyTarget == "" {
		return Config{}, errors.New("PRESTASHOP_PROXY_TARGET is required")
	}
	if cfg.PrestaShopProjectRoot == "" && cfg.PrestaShopBootstrap == "" && cfg.PrestaShopCookieKey == "" {
		return Config{}, errors.New("prestashop cookie configuration is incomplete")
	}
	if cfg.PrestaShopDBDSN == "" {
		return Config{}, errors.New("PRESTASHOP_DB_DSN is required")
	}

	return cfg, nil
}

func loadDotEnv(path string) error {
	file, err := os.Open(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil
		}
		return fmt.Errorf("open %s: %w", path, err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		if strings.HasPrefix(line, "export ") {
			line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
		}

		key, value, ok := strings.Cut(line, "=")
		if !ok {
			continue
		}

		key = strings.TrimSpace(key)
		value = strings.TrimSpace(value)
		if key == "" {
			continue
		}

		value = strings.Trim(value, `"'`)
		if _, exists := os.LookupEnv(key); exists {
			continue
		}
		if err := os.Setenv(key, value); err != nil {
			return fmt.Errorf("set env %s: %w", key, err)
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scan %s: %w", path, err)
	}

	return nil
}

func (c Config) CookieConfig() pscookie.Config {
	return pscookie.Config{
		Version:       c.PrestaShopVersion,
		CookieName:    c.DeriveCookieName(""),
		CookieKey:     c.PrestaShopCookieKey,
		CookieIV:      c.PrestaShopCookieIV,
		ProjectRoot:   c.PrestaShopProjectRoot,
		BootstrapPath: c.PrestaShopBootstrap,
	}
}

func (c Config) DeriveCookieName(host string) string {
	if c.PrestaShopCookieName != "" {
		return c.PrestaShopCookieName
	}

	domain := normalizedCookieDomain(host)
	if domain == "" {
		domain = normalizedCookieDomain(c.PrestaShopBaseURL)
	}
	if domain == "" {
		domain = normalizedCookieDomain(c.PrestaShopProxyTarget)
	}

	sum := md5.Sum([]byte(c.PrestaShopVersion + "PrestaShop" + domain))
	return fmt.Sprintf("PrestaShop-%x", sum)
}

func (c *Config) bootstrap() error {
	if c.PrestaShopProjectRoot == "" && c.PrestaShopBootstrap != "" {
		c.PrestaShopProjectRoot = filepath.Dir(filepath.Dir(c.PrestaShopBootstrap))
	}

	if c.PrestaShopProjectRoot == "" {
		return nil
	}

	settings := filepath.Join(c.PrestaShopProjectRoot, "config", "settings.inc.php")
	if data, err := os.ReadFile(settings); err == nil {
		if c.PrestaShopCookieKey == "" {
			c.PrestaShopCookieKey = parseDefine(string(data), "_COOKIE_KEY_")
		}
		if c.PrestaShopCookieIV == "" {
			c.PrestaShopCookieIV = parseDefine(string(data), "_COOKIE_IV_")
		}
		if c.PrestaShopTablePrefix == "ps_" {
			if prefix := parseDefine(string(data), "_DB_PREFIX_"); prefix != "" {
				c.PrestaShopTablePrefix = prefix
			}
		}
	}

	parameters := filepath.Join(c.PrestaShopProjectRoot, "app", "config", "parameters.php")
	if data, err := os.ReadFile(parameters); err == nil {
		values := parsePHPParameters(string(data))
		if c.PrestaShopDBDSN == "" {
			c.PrestaShopDBDSN = mysqlDSN(values["database_host"], values["database_port"], values["database_name"], values["database_user"], values["database_password"])
		}
		if c.PrestaShopCookieKey == "" {
			c.PrestaShopCookieKey = values["secret"]
		}
	}

	if c.PrestaShopBootstrap == "" {
		c.PrestaShopBootstrap = filepath.Join(c.PrestaShopProjectRoot, "config", "config.inc.php")
	}

	return nil
}

func parseDefine(input, key string) string {
	re := regexp.MustCompile(fmt.Sprintf(`define\('%s',\s*'([^']*)'`, regexp.QuoteMeta(key)))
	matches := re.FindStringSubmatch(input)
	if len(matches) < 2 {
		return ""
	}
	return matches[1]
}

func parsePHPParameters(input string) map[string]string {
	out := map[string]string{}
	re := regexp.MustCompile(`'([^']+)'\s*=>\s*'([^']*)'`)
	for _, match := range re.FindAllStringSubmatch(input, -1) {
		out[match[1]] = match[2]
	}
	return out
}

func mysqlDSN(host, port, db, user, pass string) string {
	if host == "" || db == "" || user == "" {
		return ""
	}
	if port == "" {
		port = "3306"
	}
	return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=UTC", user, pass, host, port, db)
}

func envOr(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}

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

func normalizedCookieDomain(input string) string {
	value := strings.TrimSpace(input)
	if value == "" {
		return ""
	}
	if strings.Contains(value, "://") {
		if parsed, err := url.Parse(value); err == nil {
			value = parsed.Hostname()
		}
	}
	if host, _, err := net.SplitHostPort(value); err == nil {
		value = host
	}
	value = strings.TrimPrefix(strings.ToLower(value), ".")
	value = strings.TrimPrefix(value, "www.")
	return value
}

func dbDSNFromEnv() string {
	return mysqlDSN(
		firstNonEmpty(os.Getenv("PRESTASHOP_DB_HOST"), os.Getenv("DB_HOST")),
		firstNonEmpty(os.Getenv("PRESTASHOP_DB_PORT"), os.Getenv("DB_PORT")),
		firstNonEmpty(os.Getenv("PRESTASHOP_DB_NAME"), os.Getenv("DB_NAME")),
		firstNonEmpty(os.Getenv("PRESTASHOP_DB_USER"), os.Getenv("DB_USER")),
		firstNonEmpty(os.Getenv("PRESTASHOP_DB_PASS"), os.Getenv("DB_PASS")),
	)
}

type RouteOwnership struct {
	ProductPrefixes []string `json:"product_prefixes"`
}

func (c Config) ParseRouteOwnership() RouteOwnership {
	if strings.HasPrefix(strings.TrimSpace(c.RouteOwnershipConfig), "{") {
		var parsed RouteOwnership
		if err := json.Unmarshal([]byte(c.RouteOwnershipConfig), &parsed); err == nil && len(parsed.ProductPrefixes) > 0 {
			return parsed
		}
	}

	return RouteOwnership{
		ProductPrefixes: []string{c.RouteOwnershipConfig},
	}
}
