package org.raddatz.familienarchiv.person;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.lang.Nullable;

import org.raddatz.familienarchiv.person.PersonNameAliasDTO;
import org.raddatz.familienarchiv.person.PersonSummaryDTO;
import org.raddatz.familienarchiv.person.PersonUpdateDTO;
import org.raddatz.familienarchiv.document.DatePrecision;
import org.raddatz.familienarchiv.document.DatePrecisionValidation;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonNameAlias;
import org.raddatz.familienarchiv.person.PersonNameAliasType;
import org.raddatz.familienarchiv.person.PersonType;
import org.raddatz.familienarchiv.person.PersonNameAliasRepository;
import org.raddatz.familienarchiv.person.PersonRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class PersonService {

    // Co-located with the fetch loop that owns them (issue #763). MAX_TOKENS caps the number of
    // unindexed leading-wildcard LIKE scans per name — a DoS control, not just perf. MAX_CANDIDATES
    // bounds each result bucket and is applied AFTER classification so a direct match that sorts
    // past position 10 among partials is never discarded.
    private static final int MAX_TOKENS = 8;
    private static final int MAX_CANDIDATES = 10;

    private final PersonRepository personRepository;
    private final PersonNameAliasRepository aliasRepository;

    public List<PersonSummaryDTO> findTopByDocumentCount(int limit) {
        return personRepository.findTopByDocumentCount(limit);
    }

    /**
     * Filtered, paginated directory query. The slice and the total are derived from one
     * shared WHERE clause (see {@link PersonRepository#FILTER_WHERE}) so totalElements can
     * never drift from the rendered page. {@code type} is passed as the enum name because the
     * native query compares against the string column.
     */
    public PersonSearchResult search(PersonFilter filter, int page, int size, String q) {
        String type = filter.type() == null ? null : filter.type().name();
        String query = (q == null || q.isBlank()) ? null : q.trim();
        int offset = page * size;

        List<PersonSummaryDTO> items = personRepository.findByFilter(
                type, filter.familyOnly(), filter.hasDocuments(), filter.provisional(),
                filter.readerDefault(), query, size, offset);
        long total = personRepository.countByFilter(
                type, filter.familyOnly(), filter.hasDocuments(), filter.provisional(),
                filter.readerDefault(), query);

        return PersonSearchResult.paged(items, page, size, total);
    }

    /**
     * Clears the {@code provisional} flag — a deliberate state transition exposed as
     * {@code PATCH /api/persons/{id}/confirm}, never as a mass-assignable DTO field (CWE-915).
     */
    @Transactional
    public Person confirmPerson(UUID id) {
        Person person = getById(id);
        person.setProvisional(false);
        return personRepository.save(person);
    }

    /**
     * Hard-deletes a person used by triage. Referential integrity is enforced by the database
     * (V71's {@code ON DELETE} constraints: sender_id {@code SET NULL}, receiver and @-mention
     * rows {@code CASCADE}), so the service stays thin — it only verifies existence then deletes.
     */
    @Transactional
    public void deletePerson(UUID id) {
        getById(id);
        personRepository.deleteById(id);
    }

    public Person getById(UUID id) {
        return personRepository.findById(id)
                .orElseThrow(() -> DomainException.notFound(ErrorCode.PERSON_NOT_FOUND, "Person not found: " + id));
    }

    public long count() {
        return personRepository.count();
    }

    public List<Person> findCorrespondents(UUID personId, String q) {
        if (q != null && !q.isBlank()) {
            return personRepository.findCorrespondentsWithFilter(personId, q);
        }
        return personRepository.findCorrespondents(personId);
    }

    public List<Person> getAllById(List<UUID> ids) {
        return personRepository.findAllById(ids);
    }

    public List<Person> findByDisplayNameContaining(String fragment) {
        return personRepository.searchByName(fragment);
    }

    // Name-match tokenizer (issue #763): lowercase, split on whitespace/hyphen/apostrophe,
    // drop empties. Applied symmetrically to the query and to every candidate name component so
    // that "Anna-Maria" and "Anna Maria" tokenize alike. Order-preserving for deterministic tests.
    static Set<String> tokenize(String raw) {
        if (raw == null || raw.isBlank()) {
            return Set.of();
        }
        LinkedHashSet<String> tokens = new LinkedHashSet<>();
        for (String part : raw.toLowerCase(Locale.ROOT).split("[\\s\\-']+")) {
            if (!part.isEmpty()) {
                tokens.add(part);
            }
        }
        return tokens;
    }

    /**
     * Resolves an extracted person name into {@link NameMatches} by name-match strength.
     * Orchestrates tokenize → cap → fetch pool → classify → cap-after-classify. Read-only
     * transaction keeps the Hibernate session open so each candidate's lazy {@code nameAliases}
     * are reachable during classification (see ADR-022).
     */
    @Transactional(readOnly = true)
    public NameMatches resolveByName(String name) {
        Set<String> queryTokens = capTokens(tokenize(name));
        if (queryTokens.isEmpty()) {
            log.debug("resolveByName outcome=no-match tokens=0");
            return new NameMatches(List.of(), List.of());
        }
        return classify(fetchPool(queryTokens), queryTokens);
    }

    private Set<String> capTokens(Set<String> tokens) {
        return tokens.stream().limit(MAX_TOKENS).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    private List<Person> fetchPool(Set<String> queryTokens) {
        LinkedHashMap<UUID, Person> pool = new LinkedHashMap<>();
        for (String token : queryTokens) {
            for (Person candidate : findByDisplayNameContaining(token)) {
                pool.putIfAbsent(candidate.getId(), candidate);
            }
        }
        return new ArrayList<>(pool.values());
    }

    private NameMatches classify(List<Person> pool, Set<String> queryTokens) {
        List<Person> direct = new ArrayList<>();
        List<Person> partial = new ArrayList<>();
        for (Person candidate : pool) {
            if (personTokens(candidate).containsAll(queryTokens)) {
                direct.add(candidate);
            } else {
                partial.add(candidate);
            }
        }
        List<Person> cappedDirect = cap(direct);
        List<Person> cappedPartial = cap(partial);
        log.debug("resolveByName outcome={} tokens={}", outcome(cappedDirect, cappedPartial), queryTokens.size());
        return new NameMatches(cappedDirect, cappedPartial);
    }

    private static Set<String> personTokens(Person person) {
        Set<String> tokens = new LinkedHashSet<>();
        tokens.addAll(tokenize(person.getFirstName()));
        tokens.addAll(tokenize(person.getLastName()));
        tokens.addAll(tokenize(person.getAlias()));
        tokens.addAll(tokenize(person.getTitle()));
        for (PersonNameAlias alias : person.getNameAliases()) {
            tokens.addAll(tokenize(alias.getFirstName()));
            tokens.addAll(tokenize(alias.getLastName()));
        }
        return tokens;
    }

    private static List<Person> cap(List<Person> people) {
        return people.size() > MAX_CANDIDATES ? people.subList(0, MAX_CANDIDATES) : people;
    }

    private static String outcome(List<Person> direct, List<Person> partial) {
        if (direct.size() == 1) return "direct=1";
        if (direct.size() >= 2) return "direct>=2";
        if (!partial.isEmpty()) return "partial-only";
        return "no-match";
    }

    public List<Person> findAllFamilyMembers() {
        return personRepository.findByFamilyMemberTrueOrderByLastNameAscFirstNameAsc();
    }

    public List<Person> getPersonsByGeneration(Integer generation) {
        return personRepository.findByGeneration(generation);
    }

    @Transactional
    public Person setFamilyMember(UUID personId, boolean familyMember) {
        Person person = getById(personId);
        person.setFamilyMember(familyMember);
        return personRepository.save(person);
    }

    public Optional<Person> findByName(String firstName, String lastName) {
        // Same scope as findOrCreateByAlias (#731): a case-collision resolves without throwing;
        // two byte-identical same-case persons are an out-of-scope data anomaly the exact
        // Optional below would surface as the opaque INTERNAL_ERROR, not a wrong sender.
        Optional<Person> exact = personRepository.findByFirstNameAndLastName(firstName, lastName);
        if (exact.isPresent()) return exact;
        List<Person> caseInsensitive =
                personRepository.findAllByFirstNameIgnoreCaseAndLastNameIgnoreCase(firstName, lastName);
        // Deliberate divergence from findOrCreateByAlias: an ambiguous filename leaves the sender
        // UNSET rather than picking the lowest id. The archive's value is correct provenance — a
        // confidently-wrong pre-filled "Hans Müller" is worse than an empty field, because a
        // reviewer won't re-check a pre-filled value. Do NOT "consistency-clean" this into the
        // lowest-id fallback. See ADR-033.
        return caseInsensitive.size() == 1 ? Optional.of(caseInsensitive.get(0)) : Optional.empty();
    }

    /** Lookup by the normalizer person_id — used by the canonical importer for register-first matching. */
    public Optional<Person> findBySourceRef(String sourceRef) {
        return personRepository.findBySourceRef(sourceRef);
    }

    @Nullable
    @Transactional
    public Person findOrCreateByAlias(String rawName) {
        String alias = rawName.trim();
        PersonType type = PersonTypeClassifier.classify(alias);
        if (type == PersonType.SKIP) return null;

        // Aliases differing only by case (müller / Müller) are valid distinct persons, not
        // duplicates, so a CASE-COLLISION must not throw: exact-case first, then the lowest-id
        // case-insensitive sibling, then create. Mirrors the tag path — see ADR-033.
        // Scope (#731): "ambiguous" means case-insensitive. Two BYTE-IDENTICAL same-case aliases
        // are a true data anomaly out of scope here; the exact Optional below would surface that
        // as the opaque INTERNAL_ERROR (never a wrong row), not silently pick one.
        Optional<Person> exact = personRepository.findByAlias(alias);
        if (exact.isPresent()) return exact.get();              // exact-case wins
        List<Person> caseInsensitive = personRepository.findAllByAliasIgnoreCase(alias);
        if (!caseInsensitive.isEmpty()) {
            return caseInsensitive.stream().min(Comparator.comparing(Person::getId)).orElseThrow(); // deterministic tie-break — list is non-empty, never throws
        }

        // Create-when-absent: institution/group keep the full label in lastName; a person name
        // is split and a maiden name (geb. …) becomes a MAIDEN_NAME alias.
        if (type == PersonType.INSTITUTION || type == PersonType.GROUP) {
            return personRepository.save(Person.builder()
                    .alias(alias)
                    .lastName(alias)
                    .personType(type)
                    .build());
        }

        PersonNameParser.SplitName split = PersonNameParser.split(alias);
        Person person = personRepository.save(Person.builder()
                .alias(alias)
                .firstName(split.firstName())
                .lastName(split.lastName())
                .build());
        if (split.maidenName() != null) {
            int nextSortOrder = aliasRepository.findMaxSortOrder(person.getId()) + 1;
            aliasRepository.save(PersonNameAlias.builder()
                    .person(person)
                    .lastName(split.maidenName())
                    .type(PersonNameAliasType.MAIDEN_NAME)
                    .sortOrder(nextSortOrder)
                    .build());
        }
        return person;
    }

    /**
     * Idempotent upsert keyed on {@code sourceRef} (the normalizer person_id) for the
     * canonical importer (Phase 3, ADR-025). On first import the canonical fields are
     * written verbatim. On re-import the human-edit-preserve precedence applies:
     * a non-blank existing field is never overwritten, and {@code provisional} never
     * flips back to true once a human has confirmed the person.
     */
    @Transactional
    public Person upsertBySourceRef(PersonUpsertCommand cmd) {
        return personRepository.findBySourceRef(cmd.sourceRef())
                .map(existing -> personRepository.save(mergeCanonical(existing, cmd)))
                .orElseGet(() -> fromCanonical(cmd));
    }

    private Person fromCanonical(PersonUpsertCommand cmd) {
        DatePrecisionPair none = new DatePrecisionPair(null, DatePrecision.UNKNOWN);
        LifeDates dates = degradeIfConflicting(
                yearPair(cmd.birthYear()), yearPair(cmd.deathYear()), none, none, cmd.sourceRef());
        DatePrecisionPair birth = dates.birth();
        DatePrecisionPair death = dates.death();
        Person person = personRepository.save(Person.builder()
                .sourceRef(cmd.sourceRef())
                .firstName(blankToNull(cmd.firstName()))
                .lastName(cmd.lastName())
                .notes(blankToNull(cmd.notes()))
                .birthDate(birth.date())
                .birthDatePrecision(birth.precision())
                .deathDate(death.date())
                .deathDatePrecision(death.precision())
                .generation(cmd.generation())
                .familyMember(cmd.familyMember())
                .personType(cmd.personType() == null ? PersonType.PERSON : cmd.personType())
                .provisional(cmd.provisional())
                .build());
        String maiden = blankToNull(cmd.maidenName());
        if (maiden != null) {
            int nextSortOrder = aliasRepository.findMaxSortOrder(person.getId()) + 1;
            aliasRepository.save(PersonNameAlias.builder()
                    .person(person)
                    .lastName(maiden)
                    .type(PersonNameAliasType.MAIDEN_NAME)
                    .sortOrder(nextSortOrder)
                    .build());
        }
        return person;
    }

    private Person mergeCanonical(Person existing, PersonUpsertCommand cmd) {
        existing.setFirstName(preferHuman(existing.getFirstName(), cmd.firstName()));
        existing.setLastName(preferHuman(existing.getLastName(), cmd.lastName()));
        existing.setNotes(preferHuman(existing.getNotes(), cmd.notes()));
        LifeDates dates = degradeIfConflicting(
                preferHumanDate(existing.getBirthDate(), existing.getBirthDatePrecision(), cmd.birthYear()),
                preferHumanDate(existing.getDeathDate(), existing.getDeathDatePrecision(), cmd.deathYear()),
                new DatePrecisionPair(existing.getBirthDate(), existing.getBirthDatePrecision()),
                new DatePrecisionPair(existing.getDeathDate(), existing.getDeathDatePrecision()),
                cmd.sourceRef());
        existing.setBirthDate(dates.birth().date());
        existing.setBirthDatePrecision(dates.birth().precision());
        existing.setDeathDate(dates.death().date());
        existing.setDeathDatePrecision(dates.death().precision());
        existing.setGeneration(preferHuman(existing.getGeneration(), cmd.generation()));
        if (cmd.personType() != null && existing.getPersonType() == PersonType.PERSON) {
            existing.setPersonType(cmd.personType());
        }
        // provisional is monotonic-downward: once it is false it never reverts to true.
        // This also pins the cross-loader precedence (ADR-025): a register/tree person is
        // loaded before documents and already false, so a later document row that references
        // the same source_ref (provisional=true) can never flip it provisional — the guard
        // below only fires while existing is still provisional. Order of document rows is
        // therefore irrelevant.
        if (existing.isProvisional()) {
            existing.setProvisional(cmd.provisional());
        }
        return existing;
    }

    // preferHuman keeps an existing human-entered value and only falls back to the canonical
    // value when the existing one is absent — the single idiom for every fill-blank field.
    private static String preferHuman(String existing, String canonical) {
        return (existing == null || existing.isBlank()) ? blankToNull(canonical) : existing;
    }

    private static Integer preferHuman(Integer existing, Integer canonical) {
        return existing != null ? existing : canonical;
    }

    // Date + precision travel as one value so they can never go out of sync (ADR-039).
    record DatePrecisionPair(LocalDate date, DatePrecision precision) {}

    record LifeDates(DatePrecisionPair birth, DatePrecisionPair death) {}

    // The canonical path skips validateLifeDates (the form-only guard), so a conflicting
    // resolved pair would hit chk_person_birth_before_death at flush time and abort the
    // whole import batch with a raw 500. Degrade instead (REQ-IMP-001: never abort the
    // batch): keep the person's stored life dates — empty for a new person — and drop the
    // conflicting canonical refresh. A hand-entered side is preserved by construction,
    // since preferHumanDate returned it verbatim and it equals the stored value; two
    // stored values can never conflict with each other (they already satisfied the CHECK).
    static LifeDates degradeIfConflicting(DatePrecisionPair birth, DatePrecisionPair death,
                                          DatePrecisionPair existingBirth, DatePrecisionPair existingDeath,
                                          String sourceRef) {
        if (birth.date() == null || death.date() == null || !birth.date().isAfter(death.date())) {
            return new LifeDates(birth, death);
        }
        log.warn("Conflicting canonical life dates for {}: birth {} is after death {} — keeping stored values",
                sourceRef, birth.date(), death.date());
        return new LifeDates(existingBirth, existingDeath);
    }

    // preferHuman for life dates (ADR-025 extension): a hand-entered date more precise than
    // the spreadsheet's year (DAY/MONTH/SEASON/RANGE/APPROX) is preserved on re-import; a
    // YEAR-precision or absent date is refreshed from the canonical year.
    static DatePrecisionPair preferHumanDate(LocalDate existingDate, DatePrecision existingPrecision,
                                             Integer canonicalYear) {
        boolean handEntered = existingDate != null && existingPrecision != null
                && existingPrecision != DatePrecision.YEAR && existingPrecision != DatePrecision.UNKNOWN;
        if (handEntered) {
            return new DatePrecisionPair(existingDate, existingPrecision);
        }
        return yearPair(canonicalYear);
    }

    private static DatePrecisionPair yearPair(Integer year) {
        return year != null
                ? new DatePrecisionPair(LocalDate.of(year, 1, 1), DatePrecision.YEAR)
                : new DatePrecisionPair(null, DatePrecision.UNKNOWN);
    }

    private static String blankToNull(String s) {
        return (s == null || s.isBlank()) ? null : s.trim();
    }

    @Transactional
    public Person createPerson(String firstName, String lastName, String alias) {
        Person person = Person.builder()
                .firstName(firstName)
                .lastName(lastName)
                .alias(alias == null || alias.isBlank() ? null : alias.trim())
                .build();
        return personRepository.save(person);
    }

    @Transactional
    public Person createPerson(PersonUpdateDTO dto) {
        if (dto.getPersonType() == PersonType.SKIP) {
            throw DomainException.badRequest(ErrorCode.INVALID_PERSON_TYPE, "SKIP is not a valid person type for manual creation");
        }
        validateLifeDates(dto.getBirthDate(), dto.getBirthDatePrecision(),
                dto.getDeathDate(), dto.getDeathDatePrecision());
        Person person = Person.builder()
                .personType(dto.getPersonType())
                .title(dto.getTitle() == null || dto.getTitle().isBlank() ? null : dto.getTitle().trim())
                .firstName(dto.getFirstName())
                .lastName(dto.getLastName())
                .alias(dto.getAlias() == null || dto.getAlias().isBlank() ? null : dto.getAlias().trim())
                .notes(dto.getNotes() == null || dto.getNotes().isBlank() ? null : dto.getNotes().trim())
                .birthDate(dto.getBirthDate())
                .birthDatePrecision(DatePrecisionValidation.normalize(dto.getBirthDatePrecision()))
                .deathDate(dto.getDeathDate())
                .deathDatePrecision(DatePrecisionValidation.normalize(dto.getDeathDatePrecision()))
                .generation(dto.getGeneration())
                .build();
        return personRepository.save(person);
    }

    // Cross-field invariants the V76 CHECK constraints also enforce — validated here so the
    // user gets a structured ErrorCode instead of a raw constraint-violation 500. Coherence
    // is shared with the relationship domain (DatePrecisionValidation); only the order check
    // (and its BIRTH_AFTER_DEATH code) is life-date specific.
    private void validateLifeDates(LocalDate birthDate, DatePrecision birthPrecision,
                                   LocalDate deathDate, DatePrecision deathPrecision) {
        DatePrecisionValidation.requireCoherence(birthDate, birthPrecision, "birth");
        DatePrecisionValidation.requireCoherence(deathDate, deathPrecision, "death");
        if (birthDate != null && deathDate != null && birthDate.isAfter(deathDate)) {
            throw DomainException.badRequest(ErrorCode.BIRTH_AFTER_DEATH,
                    "Birth date " + birthDate + " is after death date " + deathDate);
        }
    }

    @Transactional
    public Person updatePerson(UUID id, PersonUpdateDTO dto) {
        if (dto.getPersonType() == PersonType.SKIP) {
            throw DomainException.badRequest(ErrorCode.INVALID_PERSON_TYPE, "SKIP is not a valid person type for manual editing");
        }
        validateLifeDates(dto.getBirthDate(), dto.getBirthDatePrecision(),
                dto.getDeathDate(), dto.getDeathDatePrecision());
        Person person = personRepository.findById(id)
                .orElseThrow(() -> DomainException.notFound(ErrorCode.PERSON_NOT_FOUND, "Person not found: " + id));
        person.setPersonType(dto.getPersonType());
        person.setTitle(dto.getTitle() == null || dto.getTitle().isBlank() ? null : dto.getTitle().trim());
        person.setFirstName(dto.getFirstName());
        person.setLastName(dto.getLastName());
        person.setAlias(dto.getAlias() == null || dto.getAlias().isBlank() ? null : dto.getAlias().trim());
        person.setNotes(dto.getNotes() == null || dto.getNotes().isBlank() ? null : dto.getNotes().trim());
        person.setBirthDate(dto.getBirthDate());
        person.setBirthDatePrecision(DatePrecisionValidation.normalize(dto.getBirthDatePrecision()));
        person.setDeathDate(dto.getDeathDate());
        person.setDeathDatePrecision(DatePrecisionValidation.normalize(dto.getDeathDatePrecision()));
        // Form path: a human can clear generation back to null. Unlike the importer
        // which routes through preferHuman, we write the DTO value verbatim.
        person.setGeneration(dto.getGeneration());
        return personRepository.save(person);
    }

    /**
     * Merges the source person into the target, then deletes the source. Sender references move
     * to the target; receiver references the target lacks are inserted. The source's leftover
     * receiver join rows are not deleted explicitly — they cascade-drop via V71's
     * {@code ON DELETE CASCADE} on {@code document_receivers.person_id} when the source is deleted.
     */
    @Transactional
    public void mergePersons(UUID sourceId, UUID targetId) {
        if (sourceId.equals(targetId)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Quelle und Ziel dürfen nicht identisch sein");
        }
        personRepository.findById(sourceId)
                .orElseThrow(() -> DomainException.notFound(ErrorCode.PERSON_NOT_FOUND, "Source person not found: " + sourceId));
        personRepository.findById(targetId)
                .orElseThrow(() -> DomainException.notFound(ErrorCode.PERSON_NOT_FOUND, "Target person not found: " + targetId));

        // Reassign sender references
        personRepository.reassignSender(sourceId, targetId);

        // Add target as receiver where source is receiver but target is not yet
        personRepository.insertMissingReceiverReference(sourceId, targetId);

        // Source's remaining receiver rows cascade-drop via V71's ON DELETE CASCADE.
        personRepository.deleteById(sourceId);
    }

    // ─── Alias management ───────────────────────────────────────────────────

    public List<PersonNameAlias> getAliases(UUID personId) {
        getById(personId);
        return aliasRepository.findByPersonIdOrderBySortOrderAscCreatedAtAsc(personId);
    }

    @Transactional
    public PersonNameAlias addAlias(UUID personId, PersonNameAliasDTO dto) {
        Person person = getById(personId);
        int nextSortOrder = aliasRepository.findMaxSortOrder(personId) + 1;
        PersonNameAlias alias = PersonNameAlias.builder()
                .person(person)
                .lastName(dto.lastName())
                .firstName(dto.firstName())
                .type(dto.type())
                .sortOrder(nextSortOrder)
                .build();
        return aliasRepository.save(alias);
    }

    @Transactional
    public void removeAlias(UUID personId, UUID aliasId) {
        PersonNameAlias alias = aliasRepository.findById(aliasId)
                .orElseThrow(() -> DomainException.notFound(ErrorCode.ALIAS_NOT_FOUND, "Alias not found: " + aliasId));
        if (!alias.getPerson().getId().equals(personId)) {
            throw DomainException.forbidden("Alias does not belong to this person");
        }
        aliasRepository.delete(alias);
    }
}
