package stacks

import (
	"fmt"
	"path/filepath"
)

// smtpEnv returns the managed app-email relay env vars (as "KEY=VALUE") to inject for a
// stack, or nil when app-email does not apply. It is a pure function of settings + the
// app's smtp_mapping + the per-app toggle; the values are NEVER persisted to app.yaml —
// they are derived on every compose so a toggle change applies on the next redeploy.
//
// Gates (all must hold, else nil — §8 edge table):
//   - global app-email toggle ON (settings.AppEmailEnabled)
//   - per-app toggle ON (appEmailEnabled, from app.yaml)
//   - the app declares a usable smtp_mapping
//
// Injected: host = the in-controller shim (cfg.MailRelay.ShimHost), port = 2525
// (plaintext+STARTTLS listener), security = the app's STARTTLS term, From =
// <local>@<allowlisted-domain>, optional From display name, plus the mapping's fixed
// Extra vars (accept-invalid-cert flags, etc.).
func (m *Manager) smtpEnv(meta *Metadata, appEmailEnabled bool) []string {
	if !appEmailEnabled {
		return nil
	}
	if m.settings == nil || !m.settings.AppEmailEnabled() {
		return nil
	}
	if !meta.HasSMTPMapping() {
		return nil
	}
	sm := meta.SMTPMapping

	host := m.cfg.MailRelay.ShimHost
	if host == "" {
		host = "felhom-controller"
	}
	fromDomain := "felhom.eu"
	if len(m.cfg.MailRelay.FromDomains) > 0 && m.cfg.MailRelay.FromDomains[0] != "" {
		fromDomain = m.cfg.MailRelay.FromDomains[0]
	}
	local := sm.FromLocal
	if local == "" {
		local = meta.Slug
	}
	security := sm.SecurityValue
	if security == "" {
		security = "starttls"
	}
	port := shimPortForTLSMode(sm.TLSMode)

	out := []string{
		fmt.Sprintf("%s=%s", sm.HostVar, host),
		fmt.Sprintf("%s=%s", sm.PortVar, port),
	}
	// From: single full-address var (default) or split local-part + domain (nextcloud-style).
	if sm.FromDomainVar != "" {
		out = append(out,
			fmt.Sprintf("%s=%s", sm.FromVar, local),
			fmt.Sprintf("%s=%s", sm.FromDomainVar, fromDomain),
		)
	} else {
		out = append(out, fmt.Sprintf("%s=%s@%s", sm.FromVar, local, fromDomain))
	}
	if sm.SecurityVar != "" {
		out = append(out, fmt.Sprintf("%s=%s", sm.SecurityVar, security))
	}
	if sm.FromNameVar != "" {
		out = append(out, fmt.Sprintf("%s=%s", sm.FromNameVar, m.fromDisplayName(meta)))
	}
	for k, v := range sm.Extra {
		if k != "" {
			out = append(out, fmt.Sprintf("%s=%s", k, v))
		}
	}
	return out
}

// shimPortForTLSMode maps a mapping's tls_mode to the shim listener port. Default (empty/"starttls")
// keeps existing apps on :2525 (plaintext + STARTTLS); "plaintext" → :2526 (STARTTLS NOT advertised,
// for opportunistic-upgrade clients with no cert-skip); "implicit-tls" → :2465.
func shimPortForTLSMode(mode string) string {
	switch mode {
	case "plaintext":
		return "2526"
	case "implicit-tls":
		return "2465"
	default: // "" or "starttls"
		return "2525"
	}
}

// fromDisplayName picks the From display name: the household-set name (settings) wins,
// else the app's display name.
func (m *Manager) fromDisplayName(meta *Metadata) string {
	if m.settings != nil {
		if ae := m.settings.GetAppEmail(); ae.FromName != "" {
			return ae.FromName
		}
	}
	if meta.DisplayName != "" {
		return meta.DisplayName
	}
	return meta.Slug
}

// SetAppEmailEnabled persists the per-app email toggle in app.yaml and, if the stack is
// deployed, recreates it (docker compose up -d) so the SMTP env injection takes effect
// (toggle ON) or is removed (toggle OFF). Errors if the stack is unknown or not deployed.
func (m *Manager) SetAppEmailEnabled(name string, enabled bool) error {
	stack, ok := m.GetStack(name)
	if !ok {
		return fmt.Errorf("stack %q not found", name)
	}
	stackDir := filepath.Dir(stack.ComposePath)
	meta := LoadMetadata(stackDir)
	if !meta.HasSMTPMapping() {
		return fmt.Errorf("a(z) %q alkalmazás nem támogatja az email-küldést", name)
	}
	appCfg := LoadAppConfig(stackDir)
	if appCfg == nil || !appCfg.Deployed {
		return fmt.Errorf("a(z) %q alkalmazás nincs telepítve", name)
	}
	if appCfg.EmailEnabled == enabled {
		return nil // no change
	}
	appCfg.EmailEnabled = enabled
	if err := SaveAppConfig(stackDir, appCfg, m.encKey, SensitiveEnvVars(&meta)); err != nil {
		return fmt.Errorf("saving app config: %w", err)
	}
	m.mu.Lock()
	if s, ok := m.stacks[name]; ok {
		s.AppConfig = appCfg
	}
	m.mu.Unlock()
	m.logger.Printf("[INFO] [stacks] App-email for %s set to %v — recreating to apply", name, enabled)

	// Recreate so the (now present/absent) SMTP env is applied. stackEnv injects the relay env.
	env := m.stackEnv(stackDir)
	if _, err := m.composeExecCustomEnv(stackDir, env, "up", "-d"); err != nil {
		return fmt.Errorf("restarting to apply app-email change: %w", err)
	}
	m.logPostStartStatus(name, stackDir, env)
	return m.RefreshStatus()
}

// AppEmailStatus reports whether an app supports email and whether its per-app toggle is on.
func (m *Manager) AppEmailStatus(name string) (supported, enabled bool) {
	stack, ok := m.GetStack(name)
	if !ok {
		return false, false
	}
	stackDir := filepath.Dir(stack.ComposePath)
	meta := LoadMetadata(stackDir)
	if !meta.HasSMTPMapping() {
		return false, false
	}
	if appCfg := LoadAppConfig(stackDir); appCfg != nil {
		return true, appCfg.EmailEnabled
	}
	return true, false
}
