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

import cloud.nstart.terravault.ordinis.auth.ScopeContext;
import cloud.nstart.terravault.ordinis.domain.DataScope;
import cloud.nstart.terravault.ordinis.readapi.dto.FlattenedRecordResponse;
import cloud.nstart.terravault.ordinis.readapi.service.RecordReadService;
import cloud.nstart.terravault.ordinis.readapi.spatial.BoundingBox;
import cloud.nstart.terravault.ordinis.readapi.spatial.GeoJsonPolygon;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.OffsetDateTime;
import java.util.List;

/**
 * Read API для consumers (геопортал, Альтум). Фронт публичный — здесь
 * locale negotiation + scope filtering.
 *
 * <p>Scope authorisation: JWT claim {@code scopes} через {@link ScopeContext}.
 * Если запрос без JWT и {@code ordinis.auth.allow-query-scope=true} — fallback
 * на legacy query param {@code ?as_scope=}; иначе только PUBLIC.
 */
@RestController
@RequestMapping("/api/v1/{dictionaryName}/records")
public class DictionaryReadController {

  private final RecordReadService service;
  private final ScopeContext scopeContext;
  private final Counter scopeDeniedCounter;

  public DictionaryReadController(
      RecordReadService service, ScopeContext scopeContext, MeterRegistry meterRegistry) {
    this.service = service;
    this.scopeContext = scopeContext;
    this.scopeDeniedCounter = Counter.builder("ordinis_scope_access_denied_total")
        .description("Попытки чтения недоступного scope")
        .register(meterRegistry);
  }

  @GetMapping("/{businessKey}")
  public ResponseEntity<FlattenedRecordResponse> getActive(
      @PathVariable String dictionaryName,
      @PathVariable String businessKey,
      @RequestHeader(value = HttpHeaders.ACCEPT_LANGUAGE, required = false) String acceptLanguage,
      @RequestParam(value = "as_scope", required = false) String asScope,
      @RequestParam(value = "at", required = false) OffsetDateTime at) {

    List<DataScope> scopes = scopeContext.resolveForRead(asScope).stream().toList();
    var result = service.readActive(dictionaryName, businessKey, acceptLanguage, scopes, at);

    HttpHeaders headers = new HttpHeaders();
    if (result.chosenLocale() != null) {
      headers.add(HttpHeaders.CONTENT_LANGUAGE, result.chosenLocale());
    }
    headers.add(HttpHeaders.VARY, HttpHeaders.ACCEPT_LANGUAGE);
    return new ResponseEntity<>(result.response(), headers, 200);
  }

  @GetMapping
  public ResponseEntity<List<FlattenedRecordResponse>> list(
      @PathVariable String dictionaryName,
      @RequestHeader(value = HttpHeaders.ACCEPT_LANGUAGE, required = false) String acceptLanguage,
      @RequestParam(value = "as_scope", required = false) String asScope,
      @RequestParam(value = "at", required = false) OffsetDateTime at,
      @RequestParam(value = "bbox", required = false) String bboxCsv,
      @RequestParam(value = "polygon", required = false) String polygonGeoJson) {

    BoundingBox bbox = null;
    String polygon = null;
    if (bboxCsv != null && !bboxCsv.isBlank()) {
      try {
        bbox = BoundingBox.parse(bboxCsv);
      } catch (IllegalArgumentException e) {
        throw new BadBboxException(e.getMessage());
      }
    }
    if (polygonGeoJson != null && !polygonGeoJson.isBlank()) {
      try {
        polygon = GeoJsonPolygon.validateAndCanonicalize(polygonGeoJson);
      } catch (IllegalArgumentException e) {
        throw new BadBboxException("polygon: " + e.getMessage());
      }
    }
    if (bbox != null && polygon != null) {
      throw new BadBboxException(
          "Specify either bbox or polygon, not both");
    }

    List<DataScope> scopes = scopeContext.resolveForRead(asScope).stream().toList();
    var items = service.listActive(dictionaryName, acceptLanguage, scopes, at, bbox, polygon);

    HttpHeaders headers = new HttpHeaders();
    if (!items.isEmpty() && items.get(0)._meta() != null && items.get(0)._meta().locale() != null) {
      headers.add(HttpHeaders.CONTENT_LANGUAGE, items.get(0)._meta().locale());
    }
    headers.add(HttpHeaders.VARY, HttpHeaders.ACCEPT_LANGUAGE);
    return new ResponseEntity<>(items, headers, 200);
  }

  /** Bbox parse errors → 400, не 500. Ловится в {@code ReadApiExceptionHandler}. */
  public static class BadBboxException extends RuntimeException {
    public BadBboxException(String message) { super(message); }
  }
}
