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

import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinitionRepository;
import cloud.nstart.terravault.ordinis.events.EventEnvelope;
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionCreated;
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionUpdated;
import cloud.nstart.terravault.ordinis.outbox.OutboxRecorder;
import cloud.nstart.terravault.ordinis.restapi.dto.CreateDictionaryRequest;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class DictionaryDefinitionService {

  private final DictionaryDefinitionRepository repository;
  private final OutboxRecorder outboxRecorder;

  public DictionaryDefinitionService(
      DictionaryDefinitionRepository repository, OutboxRecorder outboxRecorder) {
    this.repository = repository;
    this.outboxRecorder = outboxRecorder;
  }

  @Transactional(readOnly = true)
  public List<DictionaryDefinition> findAll() {
    return repository.findAll();
  }

  @Transactional(readOnly = true)
  public DictionaryDefinition findByName(String name) {
    return repository.findByName(name)
        .orElseThrow(() -> OrdinisException.notFound(
            "dictionary_not_found", "Dictionary not found: " + name));
  }

  @Transactional(readOnly = true)
  public DictionaryDefinition findById(UUID id) {
    return repository.findById(id)
        .orElseThrow(() -> OrdinisException.notFound(
            "dictionary_not_found", "Dictionary not found by id: " + id));
  }

  @Transactional
  public DictionaryDefinition create(CreateDictionaryRequest req) {
    if (repository.existsByName(req.name())) {
      throw OrdinisException.conflict(
          "dictionary_already_exists", "Dictionary already exists: " + req.name());
    }

    var d = new DictionaryDefinition(UUID.randomUUID(), req.name(), req.scope(), req.schemaJson());
    d.setDisplayName(req.displayName());
    d.setDescription(req.description());
    if (req.schemaVersion() != null) d.setSchemaVersion(req.schemaVersion());
    if (req.bundle() != null) d.setBundle(req.bundle());
    if (req.supportedLocales() != null && !req.supportedLocales().isEmpty()) {
      d.setSupportedLocales(req.supportedLocales().toArray(String[]::new));
    }
    if (req.defaultLocale() != null) d.setDefaultLocale(req.defaultLocale());
    if (req.redisProjectionEnabled() != null) {
      d.setRedisProjectionEnabled(req.redisProjectionEnabled());
    }
    if (req.approvalRequired() != null) {
      d.setApprovalRequired(req.approvalRequired());
    }
    if (req.approvalMinRole() != null) {
      d.setApprovalMinRole(req.approvalMinRole().isBlank() ? null : req.approvalMinRole());
    }

    var saved = repository.save(d);

    var event = new DictionaryDefinitionCreated(
        saved.getId(),
        saved.getName(),
        saved.getDisplayName(),
        saved.getDescription(),
        toEventScope(saved.getScope()),
        saved.getSchemaJson(),
        saved.getSchemaVersion(),
        saved.getBundle(),
        saved.getSupportedLocales() == null ? List.of() : List.of(saved.getSupportedLocales()),
        saved.getDefaultLocale(),
        saved.getCreatedAt(),
        saved.getCreatedBy());

    var envelope = EventEnvelope.of(saved.getName(), toEventScope(saved.getScope()), saved.getBundle(), event);

    outboxRecorder.record(
        "DefinitionCreated",
        "DictionaryDefinition",
        saved.getId().toString(),
        saved.getName(),
        saved.getScope(),
        saved.getBundle(),
        "definition:" + saved.getName(),
        envelope);

    return saved;
  }

  @Transactional
  public DictionaryDefinition updateSchema(String name, CreateDictionaryRequest req) {
    var existing = findByName(name);
    var prevSchema = existing.getSchemaJson();

    existing.setDisplayName(req.displayName());
    existing.setDescription(req.description());
    existing.setSchemaJson(req.schemaJson());
    if (req.schemaVersion() != null) existing.setSchemaVersion(req.schemaVersion());
    if (req.supportedLocales() != null && !req.supportedLocales().isEmpty()) {
      existing.setSupportedLocales(req.supportedLocales().toArray(String[]::new));
    }
    if (req.defaultLocale() != null) existing.setDefaultLocale(req.defaultLocale());
    if (req.redisProjectionEnabled() != null) {
      existing.setRedisProjectionEnabled(req.redisProjectionEnabled());
    }
    if (req.approvalRequired() != null) {
      existing.setApprovalRequired(req.approvalRequired());
    }
    if (req.approvalMinRole() != null) {
      existing.setApprovalMinRole(
          req.approvalMinRole().isBlank() ? null : req.approvalMinRole());
    }

    var saved = repository.save(existing);

    var event = new DictionaryDefinitionUpdated(
        saved.getId(),
        saved.getName(),
        saved.getDisplayName(),
        saved.getDescription(),
        toEventScope(saved.getScope()),
        saved.getSchemaJson(),
        prevSchema,
        saved.getSchemaVersion(),
        saved.getBundle(),
        saved.getSupportedLocales() == null ? List.of() : List.of(saved.getSupportedLocales()),
        saved.getDefaultLocale(),
        saved.getUpdatedAt(),
        saved.getUpdatedBy());

    var envelope = EventEnvelope.of(saved.getName(), toEventScope(saved.getScope()), saved.getBundle(), event);

    outboxRecorder.record(
        "DefinitionUpdated",
        "DictionaryDefinition",
        saved.getId().toString(),
        saved.getName(),
        saved.getScope(),
        saved.getBundle(),
        "definition:" + saved.getName(),
        envelope);

    return saved;
  }

  private static cloud.nstart.terravault.ordinis.events.DataScope toEventScope(
      cloud.nstart.terravault.ordinis.domain.DataScope d) {
    return cloud.nstart.terravault.ordinis.events.DataScope.valueOf(d.name());
  }
}
