package cloud.nstart.terravault.ordinis.restapi.service.webhook;

import cloud.nstart.terravault.ordinis.domain.webhook.WebhookSubscription;
import cloud.nstart.terravault.ordinis.domain.webhook.WebhookSubscriptionRepository;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import cloud.nstart.terravault.ordinis.restapi.dto.webhook.CreateWebhookSubscriptionRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.SecureRandom;
import java.util.HexFormat;
import java.util.UUID;

/**
 * Service для CRUD над {@link WebhookSubscription}. HMAC secret генерируется
 * server-side через {@link SecureRandom} 32 bytes, hex-encoded в 64 chars.
 *
 * <p>Возвращается plaintext secret ТОЛЬКО при create (single-time reveal).
 * Update/list — secret замаскирован ({@code sk_****<last4>}).
 */
@Service
public class WebhookSubscriptionService {

  private static final SecureRandom RANDOM = new SecureRandom();
  private static final HexFormat HEX = HexFormat.of();

  private final WebhookSubscriptionRepository repository;

  public WebhookSubscriptionService(WebhookSubscriptionRepository repository) {
    this.repository = repository;
  }

  /**
   * Создать subscription. Возвращает entity + plaintext secret отдельно
   * чтобы caller мог вернуть его once в response.
   */
  @Transactional
  public CreateResult create(CreateWebhookSubscriptionRequest req, String createdBy) {
    String plaintextSecret = generateSecret();
    WebhookSubscription sub = new WebhookSubscription(
        req.name(),
        req.url(),
        plaintextSecret,
        createdBy);
    sub.setScopeFilter(req.scopeFilter());
    sub.setDictionaryFilter(req.dictionaryFilter());
    sub.setEventTypeFilter(req.eventTypeFilter());
    if (req.active() != null) sub.setActive(req.active());
    if (req.description() != null) sub.setDescription(req.description());

    WebhookSubscription saved = repository.save(sub);
    return new CreateResult(saved, plaintextSecret);
  }

  @Transactional(readOnly = true)
  public WebhookSubscription findById(UUID id) {
    return repository.findById(id)
        .orElseThrow(() -> OrdinisException.notFound(
            "webhook_subscription_not_found",
            "Webhook subscription not found: " + id));
  }

  @Transactional
  public WebhookSubscription update(UUID id, CreateWebhookSubscriptionRequest req) {
    WebhookSubscription sub = findById(id);
    sub.setName(req.name());
    sub.setUrl(req.url());
    sub.setScopeFilter(req.scopeFilter());
    sub.setDictionaryFilter(req.dictionaryFilter());
    sub.setEventTypeFilter(req.eventTypeFilter());
    if (req.active() != null) sub.setActive(req.active());
    if (req.description() != null) sub.setDescription(req.description());
    return repository.save(sub);
  }

  @Transactional
  public void delete(UUID id) {
    if (!repository.existsById(id)) {
      throw OrdinisException.notFound(
          "webhook_subscription_not_found",
          "Webhook subscription not found: " + id);
    }
    repository.deleteById(id);
  }

  /**
   * Rotate secret — generate new, return plaintext once. Старый secret
   * сразу не работает (FYI receiver надо обновить раньше rotate).
   */
  @Transactional
  public CreateResult rotateSecret(UUID id) {
    WebhookSubscription sub = findById(id);
    String newSecret = generateSecret();
    // Reflection не надо — добавим setter (но мы намеренно не сделали setter
    // для hmacSecret чтобы избежать случайного assignment). Используем
    // recreate pattern: тот же id, новый secret через JPA detached → save.
    WebhookSubscription rotated = new WebhookSubscription(
        sub.getName(), sub.getUrl(), newSecret, sub.getCreatedBy());
    setIdReflection(rotated, sub.getId());
    rotated.setScopeFilter(sub.getScopeFilter());
    rotated.setDictionaryFilter(sub.getDictionaryFilter());
    rotated.setEventTypeFilter(sub.getEventTypeFilter());
    rotated.setActive(sub.isActive());
    rotated.setDescription(sub.getDescription());
    WebhookSubscription saved = repository.save(rotated);
    return new CreateResult(saved, newSecret);
  }

  private static String generateSecret() {
    byte[] bytes = new byte[32]; // 256 bits
    RANDOM.nextBytes(bytes);
    return HEX.formatHex(bytes);
  }

  /** JPA fields private; нет clean setter для id поэтому reflection. */
  private static void setIdReflection(WebhookSubscription sub, UUID id) {
    try {
      var field = WebhookSubscription.class.getDeclaredField("id");
      field.setAccessible(true);
      field.set(sub, id);
    } catch (NoSuchFieldException | IllegalAccessException e) {
      throw new IllegalStateException("Failed to set id via reflection", e);
    }
  }

  public record CreateResult(WebhookSubscription subscription, String plaintextSecret) {}
}
