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

import cloud.nstart.terravault.ordinis.auth.ScopeContext;
import cloud.nstart.terravault.ordinis.domain.DataScope;
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
import cloud.nstart.terravault.ordinis.domain.record.DictionaryRecordRepository;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Snapshots service для TimeTravel slider marks. Возвращает bucketed list
 * timestamps где минимум одна запись была inserted/updated в window. UI
 * затем snap'ает slider к этим точкам — wider granularity = меньше marks
 * (clearer UX на больших windows).
 *
 * <p>Granularity = PostgreSQL date_trunc unit. Поддерживаются: hour, day,
 * week. Other unit'ы (year, month) overkill для admin scenarios (типичный
 * window 30-90 дней).
 *
 * <p>Label: backend formatits relative RU label ("1 апр", "15 апр",
 * "сейчас"). Frontend не делает locale logic — single source of truth.
 */
@Service
public class SnapshotsService {

  private static final Set<String> ALLOWED_GRANULARITY = Set.of("hour", "day", "week");
  private static final DateTimeFormatter RU_DAY = DateTimeFormatter.ofPattern("d MMM", new Locale("ru"));
  private static final DateTimeFormatter RU_HOUR = DateTimeFormatter.ofPattern("d MMM HH:mm", new Locale("ru"));

  private final DictionaryDefinitionService definitionService;
  private final DictionaryRecordRepository recordRepository;
  private final ScopeContext scopeContext;

  public SnapshotsService(
      DictionaryDefinitionService definitionService,
      DictionaryRecordRepository recordRepository,
      ScopeContext scopeContext) {
    this.definitionService = definitionService;
    this.recordRepository = recordRepository;
    this.scopeContext = scopeContext;
  }

  /**
   * @throws OrdinisException 404 если dict не существует / не виден;
   *                          400 если granularity недопустимый.
   */
  @Transactional(readOnly = true)
  public SnapshotsResponse findSnapshots(
      String name,
      OffsetDateTime from,
      OffsetDateTime to,
      String granularity) {
    DictionaryDefinition d = definitionService.findByName(name);
    if (!scopeContext.canAccess(d.getScope())) {
      throw OrdinisException.notFound(
          "dictionary_not_found", "Dictionary not found: " + name);
    }
    String gran = granularity == null ? "day" : granularity.toLowerCase();
    if (!ALLOWED_GRANULARITY.contains(gran)) {
      throw OrdinisException.badRequest(
          "invalid_granularity",
          "granularity must be one of " + ALLOWED_GRANULARITY + ", got: " + granularity);
    }
    OffsetDateTime now = OffsetDateTime.now();
    OffsetDateTime windowTo = to == null ? now : to;
    OffsetDateTime windowFrom = from == null ? windowTo.minusDays(30) : from;
    if (!windowFrom.isBefore(windowTo)) {
      throw OrdinisException.badRequest(
          "invalid_window", "from must be before to: from=" + windowFrom + " to=" + windowTo);
    }

    Set<DataScope> scopes = scopeContext.currentScopes();
    String scopesCsv = scopes.stream().map(Enum::name).collect(Collectors.joining(","));

    List<Object[]> rows = recordRepository.findSnapshotBuckets(
        d.getId(), scopesCsv, windowFrom, windowTo, gran);

    List<Snapshot> snapshots = new ArrayList<>(rows.size());
    boolean currentMarked = false;
    DateTimeFormatter fmt = "hour".equals(gran) ? RU_HOUR : RU_DAY;
    for (Object[] row : rows) {
      OffsetDateTime bucket = ((java.sql.Timestamp) row[0]).toInstant().atOffset(now.getOffset());
      long changedSinceLast = ((Number) row[1]).longValue();
      boolean isCurrent = !currentMarked && !bucket.isAfter(now);
      if (isCurrent) currentMarked = true;
      String label = isCurrent ? "сейчас" : fmt.format(bucket);
      // totalRecords остаётся 0 в MVP — точный cumulative count требует
      // отдельный window query. UI показывает только bucket + changed count.
      snapshots.add(new Snapshot(bucket, 0L, changedSinceLast, label, isCurrent));
    }
    // Reverse чтобы oldest first (UI slider читает слева-направо).
    java.util.Collections.reverse(snapshots);

    return new SnapshotsResponse(d.getName(), windowFrom, windowTo, snapshots);
  }

  // === DTOs ===

  public record Snapshot(
      OffsetDateTime at,
      long totalRecords,
      long changedSinceLast,
      String label,
      boolean isCurrent) {}

  public record SnapshotsResponse(
      String dictionary,
      OffsetDateTime from,
      OffsetDateTime to,
      List<Snapshot> snapshots) {}
}
