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

import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
import cloud.nstart.terravault.ordinis.domain.schemaworkflow.DictionarySchemaDraft;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.CreateSchemaDraftRequest;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.DecisionRequest;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.PublishDraftRequest;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.SchemaDraftResponse;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.SubmitForReviewRequest;
import cloud.nstart.terravault.ordinis.restapi.dto.schemaworkflow.UpdateSchemaDraftRequest;
import cloud.nstart.terravault.ordinis.restapi.service.DictionaryDefinitionService;
import cloud.nstart.terravault.ordinis.restapi.service.schemaworkflow.SchemaDraftService;
import jakarta.validation.Valid;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Schema workflow endpoints — pending API #3 (см.
 * docs/integration/api/pending-endpoints.md §3).
 *
 * <p>Routes:
 * <pre>
 *  POST   /api/v1/dictionaries/{name}/drafts
 *  POST   /api/v1/dictionaries/{name}/drafts/{draftId}/review
 *  POST   /api/v1/dictionaries/{name}/drafts/{draftId}/decision
 *  POST   /api/v1/dictionaries/{name}/drafts/{draftId}/publish
 *  DELETE /api/v1/dictionaries/{name}/drafts/{draftId}         (withdraw)
 *  GET    /api/v1/dictionaries/{name}/drafts
 *  GET    /api/v1/dictionaries/{name}/drafts/active
 *  GET    /api/v1/dictionaries/{name}/drafts/{draftId}
 *  GET    /api/v1/admin/schema-reviews/pending                  (pool queue)
 *  GET    /api/v1/admin/schema-drafts/me                         (maker tracking)
 * </pre>
 *
 * <p>{@code {name}} в path передаётся для красивых URL'ов + кэширования
 * frontend query keys; внутри service resolve'им dict_id через
 * {@link DictionaryDefinitionService#findByName}.
 */
@RestController
public class SchemaDraftController {

  private final SchemaDraftService draftService;
  private final DictionaryDefinitionService definitionService;

  public SchemaDraftController(
      SchemaDraftService draftService,
      DictionaryDefinitionService definitionService) {
    this.draftService = draftService;
    this.definitionService = definitionService;
  }

  /**
   * Create draft от current HEAD schema. Returns 201 с full draft state.
   * <ul>
   *   <li>409 {@code version_mismatch} — fromVersion не = current HEAD.</li>
   *   <li>409 {@code active_draft_exists} — другой draft уже non-terminal
   *       на этом dict'е.</li>
   * </ul>
   */
  @PostMapping("/api/v1/dictionaries/{name}/drafts")
  @ResponseStatus(HttpStatus.CREATED)
  public SchemaDraftResponse create(
      @PathVariable String name,
      @Valid @RequestBody CreateSchemaDraftRequest req) {
    DictionarySchemaDraft draft = draftService.create(name, req);
    return SchemaDraftResponse.from(draft, name);
  }

  /**
   * Submit draft → review_pending. Transitions: DRAFT → REVIEW_PENDING,
   * CHANGES_REQUESTED → REVIEW_PENDING (re-submit).
   *
   * <p>Returns 202 Accepted — async wait для reviewer'а decision.
   */
  @PostMapping("/api/v1/dictionaries/{name}/drafts/{draftId}/review")
  @ResponseStatus(HttpStatus.ACCEPTED)
  public SchemaDraftResponse submitForReview(
      @PathVariable String name,
      @PathVariable UUID draftId,
      @Valid @RequestBody(required = false) SubmitForReviewRequest req) {
    SubmitForReviewRequest body = req == null ? new SubmitForReviewRequest(null, null) : req;
    DictionarySchemaDraft draft = draftService.submitForReview(draftId, body);
    return SchemaDraftResponse.from(draft, name);
  }

  /**
   * Reviewer decision (approve / request_changes / reject).
   * <ul>
   *   <li>403 {@code self_approve_forbidden} — actor == maker.</li>
   *   <li>409 {@code invalid_transition} — draft не в review_pending.</li>
   * </ul>
   */
  @PostMapping("/api/v1/dictionaries/{name}/drafts/{draftId}/decision")
  public SchemaDraftResponse decide(
      @PathVariable String name,
      @PathVariable UUID draftId,
      @Valid @RequestBody DecisionRequest req) {
    DictionarySchemaDraft draft = draftService.decide(draftId, req);
    return SchemaDraftResponse.from(draft, name);
  }

  /**
   * Publish approved draft → live HEAD.
   *
   * <p>Bump'ит schema version (minor default, major на breaking changes).
   * <ul>
   *   <li>409 {@code invalid_transition} — draft не approved.</li>
   *   <li>409 {@code version_mismatch} — expectedHeadVersion stale.</li>
   * </ul>
   */
  @PostMapping("/api/v1/dictionaries/{name}/drafts/{draftId}/publish")
  public Map<String, Object> publish(
      @PathVariable String name,
      @PathVariable UUID draftId,
      @Valid @RequestBody PublishDraftRequest req) {
    DictionarySchemaDraft draft = draftService.publish(draftId, req);
    return Map.of(
        "draft", SchemaDraftResponse.from(draft, name),
        "newVersion", draft.getPublishedVersion(),
        "publishedAt", draft.getPublishedAt());
  }

  /**
   * Partial update content draft'а без recreate. Status-gate:
   * только DRAFT и CHANGES_REQUESTED. CHANGES_REQUESTED → DRAFT
   * автоматически после update (maker должен ре-submit).
   *
   * <p>409 {@code invalid_transition} — review_pending / approved /
   * terminal. 403 {@code maker_only} — actor != maker.
   */
  @org.springframework.web.bind.annotation.PatchMapping("/api/v1/dictionaries/{name}/drafts/{draftId}")
  public SchemaDraftResponse update(
      @PathVariable String name,
      @PathVariable UUID draftId,
      @Valid @RequestBody UpdateSchemaDraftRequest req) {
    DictionarySchemaDraft draft = draftService.updateContent(draftId, req);
    return SchemaDraftResponse.from(draft, name);
  }

  /** Withdraw — maker отменяет non-terminal draft. */
  @DeleteMapping("/api/v1/dictionaries/{name}/drafts/{draftId}")
  public SchemaDraftResponse withdraw(
      @PathVariable String name,
      @PathVariable UUID draftId) {
    DictionarySchemaDraft draft = draftService.withdraw(draftId);
    return SchemaDraftResponse.from(draft, name);
  }

  /** List all drafts for a dictionary (newest first). */
  @GetMapping("/api/v1/dictionaries/{name}/drafts")
  public List<SchemaDraftResponse> list(@PathVariable String name) {
    return draftService.listByDictionary(name).stream()
        .map(d -> SchemaDraftResponse.from(d, name))
        .toList();
  }

  /**
   * Convenience endpoint: фронт хочет ОДИН query'ом узнать "есть ли active
   * draft на этом dict'е и в каком он state'е". Returns 200 с draft body
   * либо 404 если нет active.
   */
  @GetMapping("/api/v1/dictionaries/{name}/drafts/active")
  public SchemaDraftResponse active(@PathVariable String name) {
    DictionarySchemaDraft draft = draftService.findActive(name).orElseThrow(() ->
        cloud.nstart.terravault.ordinis.restapi.error.OrdinisException.notFound(
            "no_active_draft",
            "У справочника " + name + " нет активного draft'а."));
    return SchemaDraftResponse.from(draft, name);
  }

  /** Single draft по id (drilling в reviewer queue). */
  @GetMapping("/api/v1/dictionaries/{name}/drafts/{draftId}")
  public SchemaDraftResponse getById(
      @PathVariable String name,
      @PathVariable UUID draftId) {
    return SchemaDraftResponse.from(draftService.findById(draftId), name);
  }

  /**
   * Global pool queue для approvers — все review_pending drafts across all
   * dicts, sorted oldest-first (fairness).
   */
  @GetMapping("/api/v1/admin/schema-reviews/pending")
  public Map<String, Object> globalQueue(
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "50") int size) {
    var pg = draftService.listPendingReview(PageRequest.of(page, Math.min(size, 200)));
    List<SchemaDraftResponse> items = pg.getContent().stream()
        .map(d -> {
          // Optionally enrich с dictionary name — но это N+1 для большого queue;
          // в pool view name может resolveить frontend через separate /dicts query.
          DictionaryDefinition def = definitionService.findById(d.getDictionaryId());
          return SchemaDraftResponse.from(d, def.getName());
        })
        .toList();
    return Map.of(
        "items", items,
        "page", pg.getNumber(),
        "size", pg.getSize(),
        "totalElements", pg.getTotalElements(),
        "totalPages", pg.getTotalPages());
  }

  /**
   * Maker self-tracking — все schema drafts актора, любой статус.
   * Зеркало record-side {@code /api/v1/drafts/me}. Frontend "Мои черновики"
   * объединяет обе очереди (record + schema), чтобы пользователь не
   * запоминал какой тип где лежит.
   *
   * <p>Sorted createdAt DESC (newest first) — самые свежие drafts наверху,
   * legacy / approved уходят вниз. UI добавляет фильтр по status.
   */
  @GetMapping("/api/v1/admin/schema-drafts/me")
  public Map<String, Object> myDrafts(
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "50") int size) {
    String makerId = currentUserId();
    var pg = draftService.listByMaker(makerId, PageRequest.of(page, Math.min(size, 200)));
    List<SchemaDraftResponse> items = pg.getContent().stream()
        .map(d -> {
          DictionaryDefinition def = definitionService.findById(d.getDictionaryId());
          return SchemaDraftResponse.from(d, def.getName());
        })
        .toList();
    return Map.of(
        "items", items,
        "page", pg.getNumber(),
        "size", pg.getSize(),
        "totalElements", pg.getTotalElements(),
        "totalPages", pg.getTotalPages());
  }

  /** Resolve current user from JWT subject. Fallback "anonymous" если auth off. */
  private static String currentUserId() {
    var auth = org.springframework.security.core.context.SecurityContextHolder
        .getContext().getAuthentication();
    if (auth == null || !auth.isAuthenticated()) return "anonymous";
    return auth.getName();
  }
}
