package secrets

import (
	"context"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/vault/kv"
	"go.uber.org/zap"
)

const (
	defaultVaultField = "value"
	vaultRefPrefix    = "vault:"
)

type cacheEntry struct {
	value     string
	expiresAt time.Time
}

type provider struct {
	logger        mlogger.Logger
	static        map[string]string
	ttl           time.Duration
	vault         kv.Client
	vaultEnabled  bool
	vaultDefField string

	mu    sync.RWMutex
	cache map[string]cacheEntry
}

func newProvider(opts Options) (Provider, error) {
	logger := opts.Logger
	if logger == nil {
		logger = zap.NewNop()
	}

	static := map[string]string{}
	for k, v := range opts.Static {
		key := strings.TrimSpace(k)
		if key == "" {
			continue
		}
		static[key] = v
	}

	vaultField := strings.TrimSpace(opts.Vault.DefaultField)
	if vaultField == "" {
		vaultField = defaultVaultField
	}

	var vaultClient kv.Client
	vaultEnabled := false
	hasVaultConfig := strings.TrimSpace(opts.Vault.Config.Address) != "" ||
		strings.TrimSpace(opts.Vault.Config.TokenEnv) != "" ||
		strings.TrimSpace(opts.Vault.Config.MountPath) != ""
	if hasVaultConfig {
		client, err := kv.New(kv.Options{
			Logger:    logger.Named("vault"),
			Config:    opts.Vault.Config,
			Component: "callbacks secrets",
		})
		if err != nil {
			return nil, err
		}
		vaultClient = client
		vaultEnabled = true
	}

	return &provider{
		logger:        logger.Named("secrets"),
		static:        static,
		ttl:           opts.CacheTTL,
		vault:         vaultClient,
		vaultEnabled:  vaultEnabled,
		vaultDefField: vaultField,
		cache:         map[string]cacheEntry{},
	}, nil
}

func (p *provider) GetSecret(ctx context.Context, ref string) (string, error) {
	key := strings.TrimSpace(ref)
	if key == "" {
		return "", merrors.InvalidArgument("secret reference is required", "secret_ref")
	}
	if ctx == nil {
		ctx = context.Background()
	}

	if value, ok := p.fromCache(key); ok {
		return value, nil
	}

	value, err := p.resolve(ctx, key)
	if err != nil {
		return "", err
	}
	if strings.TrimSpace(value) == "" {
		return "", merrors.NoData("secret reference resolved to empty value")
	}

	p.toCache(key, value)
	return value, nil
}

func (p *provider) resolve(ctx context.Context, key string) (string, error) {
	if value, ok := p.static[key]; ok {
		return value, nil
	}
	if strings.HasPrefix(key, "env:") {
		envKey := strings.TrimSpace(strings.TrimPrefix(key, "env:"))
		if envKey == "" {
			return "", merrors.InvalidArgument("secret env reference is invalid", "secret_ref")
		}
		value := strings.TrimSpace(os.Getenv(envKey))
		if value == "" {
			return "", merrors.NoData("secret env variable not set: " + envKey)
		}
		return value, nil
	}

	if strings.HasPrefix(strings.ToLower(key), vaultRefPrefix) && !p.vaultEnabled {
		return "", merrors.InvalidArgument("vault secret reference provided but vault is not configured", "secret_ref")
	}
	if p.vaultEnabled {
		value, resolved, err := p.resolveVault(ctx, key)
		if err != nil {
			return "", err
		}
		if resolved {
			return value, nil
		}
	}

	return "", merrors.NoData("secret reference not found: " + key)
}

func (p *provider) resolveVault(ctx context.Context, ref string) (string, bool, error) {
	path, field, resolved, err := parseVaultRef(ref, p.vaultDefField)
	if err != nil {
		return "", false, err
	}
	if !resolved {
		return "", false, nil
	}

	value, err := p.vault.GetString(ctx, path, field)
	if err != nil {
		p.logger.Warn("Failed to resolve vault secret", zap.String("path", path), zap.String("field", field), zap.Error(err))
		return "", true, err
	}
	return value, true, nil
}

func parseVaultRef(ref, defaultField string) (string, string, bool, error) {
	raw := strings.TrimSpace(ref)
	lowered := strings.ToLower(raw)
	explicit := false
	if strings.HasPrefix(lowered, vaultRefPrefix) {
		explicit = true
		raw = strings.TrimSpace(raw[len(vaultRefPrefix):])
	}

	if !explicit && !strings.Contains(raw, "/") && !strings.Contains(raw, "#") {
		return "", "", false, nil
	}

	field := strings.TrimSpace(defaultField)
	if field == "" {
		field = defaultVaultField
	}

	if idx := strings.Index(raw, "#"); idx >= 0 {
		field = strings.TrimSpace(raw[idx+1:])
		raw = strings.TrimSpace(raw[:idx])
		if field == "" {
			return "", "", false, merrors.InvalidArgument("vault secret field is required", "secret_ref")
		}
	}

	path := strings.Trim(strings.TrimSpace(raw), "/")
	if path == "" {
		return "", "", false, merrors.InvalidArgument("vault secret path is required", "secret_ref")
	}

	return path, field, true, nil
}

func (p *provider) fromCache(key string) (string, bool) {
	if p.ttl <= 0 {
		return "", false
	}
	p.mu.RLock()
	entry, ok := p.cache[key]
	p.mu.RUnlock()
	if !ok {
		return "", false
	}
	if time.Now().After(entry.expiresAt) {
		p.mu.Lock()
		delete(p.cache, key)
		p.mu.Unlock()
		return "", false
	}
	return entry.value, true
}

func (p *provider) toCache(key, value string) {
	if p.ttl <= 0 {
		return
	}
	p.mu.Lock()
	p.cache[key] = cacheEntry{
		value:     value,
		expiresAt: time.Now().Add(p.ttl),
	}
	p.mu.Unlock()
}
