package cloud.nstart.terravault.ordinis.notifications.template;

import cloud.nstart.terravault.ordinis.notifications.channel.ChannelKind;
import cloud.nstart.terravault.ordinis.notifications.channel.RenderedMessage;
import cloud.nstart.terravault.ordinis.notifications.sanitizer.Sanitizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.stereotype.Component;

import java.util.Locale;
import java.util.Map;

/**
 * Renders notification messages из Spring {@code MessageSource} bundles.
 *
 * <p>Bundles в classpath {@code messages/notifications_{locale}.properties}.
 * Keys имеют shape {@code {eventKey}.{channel}.{slot}}, where:
 * <ul>
 *   <li>{@code eventKey}: 'draft_submitted' | 'draft_decision' | 'draft_withdrawn'</li>
 *   <li>{@code channel}: 'email' | 'express' | 'telegram'</li>
 *   <li>{@code slot}: 'subject' | 'body'</li>
 * </ul>
 *
 * <p>Example: {@code draft_submitted.email.subject} = {@code "[НСИ] Новый draft на review — {0} / {1}"}
 *
 * <p>Sanitization built-in: каждый dynamic param проходит {@code Sanitizer.forXxx()}
 * per channel kind ДО {@code MessageSource.getMessage()}. Defence-in-depth — даже
 * если template содержит markdown, sanitized params не сломают rendering.
 */
// Gate must mirror NotificationsAutoConfiguration. Writer's
// @SpringBootApplication scanBasePackages — superset, может ловить этот
// @Component до того как notifications сконфигурированы. Plus
// @Qualifier на конструкторе — Spring Boot auto-creates default
// `messageSource` bean (для app-wide i18n) → ambiguous-by-type injection без
// явного qualifier'а.
@Component
@ConditionalOnProperty(name = "ordinis.notifications.enabled", havingValue = "true", matchIfMissing = false)
public class MessageRenderer {

  private static final Logger log = LoggerFactory.getLogger(MessageRenderer.class);

  /** Default locale если recipient.locale=null или unsupported. */
  public static final Locale DEFAULT_LOCALE = new Locale("ru");

  private final MessageSource messageSource;

  public MessageRenderer(@Qualifier("notificationsMessageSource") MessageSource messageSource) {
    this.messageSource = messageSource;
  }

  /**
   * Render subject + body для (eventKey, channel, locale) с sanitized params.
   *
   * <p>Params порядок MUST соответствовать {0} {1} {2}... placeholders в template
   * (Java MessageFormat conventions). См. notifications_ru.properties для reference.
   *
   * @param eventKey 'draft_submitted' | 'draft_decision' | 'draft_withdrawn'
   * @param channel target channel — определяет sanitization rules
   * @param locale recipient locale (RU/EN), fallback DEFAULT_LOCALE
   * @param params positional params {0..N} (sanitization применяется автоматически
   *               через {@link #sanitizeForChannel})
   * @return RenderedMessage с subject + body
   * @throws NoSuchMessageException если template missing — лучше fail loud чем
   *   silent empty render
   */
  public RenderedMessage render(
      String eventKey, ChannelKind channel, Locale locale, Object... params) {

    Locale effectiveLocale = (locale != null) ? locale : DEFAULT_LOCALE;
    Object[] safe = sanitizeForChannel(channel, params);

    String channelKey = channel.wireName();
    String subjectKey = eventKey + "." + channelKey + ".subject";
    String bodyKey = eventKey + "." + channelKey + ".body";

    String subject = messageSource.getMessage(subjectKey, safe, effectiveLocale);
    String body = messageSource.getMessage(bodyKey, safe, effectiveLocale);

    return new RenderedMessage(subject, body);
  }

  /**
   * Apply per-channel sanitization. String params get channel-appropriate escape;
   * non-string params (numbers, dates) pass through.
   */
  private Object[] sanitizeForChannel(ChannelKind channel, Object[] params) {
    if (params == null) return new Object[0];
    Object[] out = new Object[params.length];
    for (int i = 0; i < params.length; i++) {
      Object p = params[i];
      if (p instanceof String s) {
        out[i] = switch (channel) {
          case EMAIL -> Sanitizer.forEmailHeader(s);
          case EXPRESS -> Sanitizer.forExpressText(s);
          case TELEGRAM -> Sanitizer.forTelegramMarkdown(s);
        };
      } else {
        out[i] = p;
      }
    }
    return out;
  }
}
