package cloud.nstart.terravault.ordinis.restapi.web;

import cloud.nstart.terravault.ordinis.domain.notification.UserNotificationPref;
import cloud.nstart.terravault.ordinis.domain.notification.UserNotificationPrefRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Phase B — per-user channel preferences endpoint. Phase A был channel-agnostic
 * (всем по email если subscribed in keycloak). Phase B даёт пользователю явный
 * toggle per channel.
 *
 * <p>Schema (migration 0021 changeSet 0021-2): {@code user_notification_prefs
 * (user_id VARCHAR(128), channel VARCHAR(16), enabled BOOLEAN)} с PK
 * (user_id, channel). Channel CHECK constraint: email / express / telegram.
 *
 * <p>Default semantics when row missing: EMAIL ON, EXPRESS/TELEGRAM OFF.
 * Mirrors {@link cloud.nstart.terravault.ordinis.notifications.dispatcher.UserPreferencesGate}.
 *
 * <p>Endpoints (both require auth — anonymous → 401):
 * <ul>
 *   <li>{@code GET /api/v1/me/notifications/preferences} — returns current state
 *       as {@code {email: bool, express: bool, telegram: bool}}. Missing rows
 *       resolve через defaults.</li>
 *   <li>{@code PUT /api/v1/me/notifications/preferences} — upsert all three
 *       channels. Idempotent. Returns echoed state.</li>
 * </ul>
 *
 * <p>NB: nginx routing трап (см. comment в admin-ui nginx.conf) — endpoint
 * под {@code /api/v1/me/*} требует explicit route к writer (ordinis-app).
 * Generic {@code /api/v1/*} GET fallback идёт в read-api → 500. Already
 * mitigated через {@code /api/v1/me} location block добавленный в MR !197
 * (см. nginx.conf).
 */
@RestController
@RequestMapping("/api/v1/me/notifications/preferences")
public class MeNotificationsPreferencesController {

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

  private static final String CHAN_EMAIL = "email";
  private static final String CHAN_EXPRESS = "express";
  private static final String CHAN_TELEGRAM = "telegram";

  private final UserNotificationPrefRepository repo;

  public MeNotificationsPreferencesController(UserNotificationPrefRepository repo) {
    this.repo = repo;
  }

  /** Read current preferences. Missing rows resolve через defaults (email ON, others OFF). */
  @GetMapping
  public PreferencesDto get() {
    String sub = requireSub();
    Map<String, Boolean> saved = new HashMap<>();
    for (UserNotificationPref row : repo.findByIdUserId(sub)) {
      saved.put(row.getChannel(), row.isEnabled());
    }
    return new PreferencesDto(
        saved.getOrDefault(CHAN_EMAIL, true),       // default ON
        saved.getOrDefault(CHAN_EXPRESS, false),    // default OFF (channel TBD)
        saved.getOrDefault(CHAN_TELEGRAM, false)    // default OFF (channel TBD)
    );
  }

  /**
   * Upsert all channel toggles. Always writes three rows (one per channel)
   * чтобы DB state matched UI state — иначе "missing row" semantics утечет в
   * surprise (e.g. user expects "email OFF" но row missing → dispatcher sees
   * default-ON).
   */
  @PutMapping
  @Transactional
  public PreferencesDto update(@RequestBody PreferencesDto body) {
    String sub = requireSub();
    if (body == null) {
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "request body required");
    }
    upsert(sub, CHAN_EMAIL, body.email());
    upsert(sub, CHAN_EXPRESS, body.express());
    upsert(sub, CHAN_TELEGRAM, body.telegram());
    log.info("Notification prefs updated for user={}: email={}, express={}, telegram={}",
        sub, body.email(), body.express(), body.telegram());
    return new PreferencesDto(body.email(), body.express(), body.telegram());
  }

  private void upsert(String userId, String channel, boolean enabled) {
    UserNotificationPref.Pk pk = new UserNotificationPref.Pk(userId, channel);
    UserNotificationPref existing = repo.findById(pk).orElse(null);
    if (existing == null) {
      repo.save(new UserNotificationPref(userId, channel, enabled));
    } else if (existing.isEnabled() != enabled) {
      existing.setEnabled(enabled);
      repo.save(existing);
    }
  }

  /** Pull sub claim from JWT or 401. */
  private String requireSub() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (!(auth instanceof JwtAuthenticationToken jwt)) {
      throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "JWT required");
    }
    String sub = jwt.getToken().getSubject();
    if (sub == null || sub.isBlank()) {
      throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "JWT sub claim missing");
    }
    return sub;
  }

  /**
   * Response/request shape — flat boolean per channel. Extensible (future
   * channels добавятся как новые fields, backwards-compatible because JSON
   * deserialization tolerates missing fields с record defaults). Per-event-type
   * granularity defer'нута — потребует schema migration с {@code event_type}
   * column на user_notification_prefs.
   */
  public record PreferencesDto(boolean email, boolean express, boolean telegram) {}
}
