package org.raddatz.familienarchiv.person;

import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.person.PersonNameAliasType;
import org.raddatz.familienarchiv.person.PersonType;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class PersonNameParserTest {

    // --- parseReceivers ---

    @Test
    void singlePerson_noChange() {
        assertThat(PersonNameParser.parseReceivers("Walter de Gruyter"))
                .containsExactly("Walter de Gruyter");
    }

    @Test
    void gebAnnotation_stripped() {
        assertThat(PersonNameParser.parseReceivers("Eugenie de Gruyter geb. Müller"))
                .containsExactly("Eugenie de Gruyter");
    }

    @Test
    void singlePerson_annotationParenPreserved() {
        assertThat(PersonNameParser.parseReceivers("Clara de Gruyter(*1871)"))
                .containsExactly("Clara de Gruyter(*1871)");
    }

    @Test
    void singlePerson_nicknameParenPreserved() {
        assertThat(PersonNameParser.parseReceivers("Gertrud D.(Tuttu)"))
                .containsExactly("Gertrud D.(Tuttu)");
    }

    @Test
    void gebAnnotation_noDot_multiWord_stripped() {
        assertThat(PersonNameParser.parseReceivers("Ella Dieckmann, geb de Gruyter"))
                .containsExactly("Ella Dieckmann");
    }

    @Test
    void gebAnnotation_noDot_singleWord_stripped() {
        assertThat(PersonNameParser.parseReceivers("Elise Rockstroh geb Sintenis"))
                .containsExactly("Elise Rockstroh");
    }

    @Test
    void twoFirstNames_sharedKnownLastName_und() {
        assertThat(PersonNameParser.parseReceivers("Walter und Eugenie de Gruyter"))
                .containsExactly("Walter de Gruyter", "Eugenie de Gruyter");
    }

    @Test
    void twoFirstNames_sharedKnownLastName_u() {
        assertThat(PersonNameParser.parseReceivers("Herbert und Clara Cram"))
                .containsExactly("Herbert Cram", "Clara Cram");
    }

    @Test
    void twoFirstNames_sharedKnownLastName_u_short() {
        assertThat(PersonNameParser.parseReceivers("Ella u Walter Dieckmann"))
                .containsExactly("Ella Dieckmann", "Walter Dieckmann");
    }

    @Test
    void twoFirstNames_parenthesisedLastName() {
        assertThat(PersonNameParser.parseReceivers("Hedi und Tutu (Gruber)"))
                .containsExactly("Hedi Gruber", "Tutu Gruber");
    }

    @Test
    void twoPersons_differentLastNames() {
        assertThat(PersonNameParser.parseReceivers("Clara Cram u Ellen B-M"))
                .containsExactly("Clara Cram", "Ellen B-M");
    }

    @Test
    void familie_filtered_out() {
        assertThat(PersonNameParser.parseReceivers("Clara u Familie"))
                .containsExactly("Clara");
    }

    @Test
    void twoFirstNames_noLastName() {
        assertThat(PersonNameParser.parseReceivers("Walter und Eugenie"))
                .containsExactly("Walter", "Eugenie");
    }

    @Test
    void nullInput_returnsEmpty() {
        assertThat(PersonNameParser.parseReceivers(null)).isEmpty();
    }

    @Test
    void blankInput_returnsEmpty() {
        assertThat(PersonNameParser.parseReceivers("   ")).isEmpty();
    }

    // --- split ---

    @Test
    void split_knownMultiWordLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Walter de Gruyter");
        assertThat(result.firstName()).isEqualTo("Walter");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
        assertThat(result.title()).isNull();
        assertThat(result.maidenName()).isNull();
        assertThat(result.annotation()).isNull();
    }

    @Test
    void split_knownSingleWordLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Clara Cram");
        assertThat(result.firstName()).isEqualTo("Clara");
        assertThat(result.lastName()).isEqualTo("Cram");
    }

    @Test
    void split_unknownLastName_fallsBackToLastSpace() {
        PersonNameParser.SplitName result = PersonNameParser.split("Ellen Burkhard-Meier");
        assertThat(result.firstName()).isEqualTo("Ellen");
        assertThat(result.lastName()).isEqualTo("Burkhard-Meier");
    }

    @Test
    void split_singleToken_lastNameIsPlaceholder() {
        PersonNameParser.SplitName result = PersonNameParser.split("Clara");
        assertThat(result.firstName()).isEqualTo("Clara");
        assertThat(result.lastName()).isEqualTo("?");
    }

    @Test
    void split_gebAnnotation_stripped() {
        PersonNameParser.SplitName result = PersonNameParser.split("Eugenie de Gruyter geb. Müller");
        assertThat(result.title()).isNull();
        assertThat(result.firstName()).isEqualTo("Eugenie");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
        assertThat(result.maidenName()).isEqualTo("Müller");
        assertThat(result.annotation()).isNull();
    }

    @Test
    void split_null_returnsPlaceholder() {
        PersonNameParser.SplitName result = PersonNameParser.split(null);
        assertThat(result.title()).isNull();
        assertThat(result.firstName()).isEqualTo("?");
        assertThat(result.lastName()).isEqualTo("?");
        assertThat(result.maidenName()).isNull();
        assertThat(result.annotation()).isNull();
    }

    @Test
    void split_blank_returnsPlaceholder() {
        PersonNameParser.SplitName result = PersonNameParser.split("   ");
        assertThat(result.title()).isNull();
        assertThat(result.firstName()).isEqualTo("?");
        assertThat(result.lastName()).isEqualTo("?");
        assertThat(result.maidenName()).isNull();
        assertThat(result.annotation()).isNull();
    }

    @Test
    void split_onlyKnownLastName_firstNameFallsBackToCleaned() {
        // "de Gruyter" alone → firstName would be blank after removing last name, so cleaned is used
        PersonNameParser.SplitName result = PersonNameParser.split("de Gruyter");
        assertThat(result.firstName()).isEqualTo("de Gruyter");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
    }

    // --- split — dot-compressed names ---

    @Test
    void split_dotCompressed_initialAndLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("E.Rockstroh");
        assertThat(result.firstName()).isEqualTo("E.");
        assertThat(result.lastName()).isEqualTo("Rockstroh");
    }

    @Test
    void split_dotCompressed_twoInitials() {
        PersonNameParser.SplitName result = PersonNameParser.split("E.M.");
        assertThat(result.firstName()).isEqualTo("E.");
        assertThat(result.lastName()).isEqualTo("M.");
    }

    @Test
    void split_dotCompressed_titleFirstNameLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Dr.Fr.Zarncke");
        assertThat(result.title()).isEqualTo("Dr.");
        assertThat(result.firstName()).isEqualTo("Fr.");
        assertThat(result.lastName()).isEqualTo("Zarncke");
    }

    @Test
    void split_dotCompressed_titleAndLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Dr.Zarnke");
        assertThat(result.title()).isEqualTo("Dr.");
        assertThat(result.firstName()).isNull();
        assertThat(result.lastName()).isEqualTo("Zarnke");
    }

    @Test
    void parseReceivers_dotCompressedName_passthrough() {
        assertThat(PersonNameParser.parseReceivers("Dr.Fr.Zarncke"))
                .containsExactly("Dr.Fr.Zarncke");
    }

    @Test
    void split_alreadySpacedDotName_noDoubleSpacing() {
        PersonNameParser.SplitName result = PersonNameParser.split("Dr. Fr. Zarncke");
        assertThat(result.title()).isEqualTo("Dr.");
        assertThat(result.firstName()).isEqualTo("Fr.");
        assertThat(result.lastName()).isEqualTo("Zarncke");
    }

    @Test
    void slashSeparator_combinedWithDotCompressed() {
        assertThat(PersonNameParser.parseReceivers("E.Rockstroh//Dr.Fr.Zarncke"))
                .containsExactly("E.Rockstroh", "Dr.Fr.Zarncke");
    }

    // --- parseReceivers — shared last name with full-name part ─────────────────

    @Test
    void parseReceivers_partWithSpace_notAppended_whenParenLastNamePresent() {
        // "Clara Cram und Hans (Müller)": Clara Cram already has a space → keep as-is
        List<String> result = PersonNameParser.parseReceivers("Clara Cram und Hans (Müller)");
        assertThat(result).containsExactlyInAnyOrder("Clara Cram", "Hans Müller");
    }

    @Test
    void parseReceivers_partAlreadyFullName_notDistributed_fromLastSegmentLastName() {
        // "Clara Cram und Eugenie de Gruyter": first part has its own name, no distribution
        List<String> result = PersonNameParser.parseReceivers("Clara Cram und Eugenie de Gruyter");
        assertThat(result).containsExactlyInAnyOrder("Clara Cram", "Eugenie de Gruyter");
    }

    // --- parseReceivers — // separator ---

    @Test
    void slashSeparator_twoIndependentFullNames() {
        assertThat(PersonNameParser.parseReceivers("Charl.Blomquist//Tante Lolly"))
                .containsExactly("Charl.Blomquist", "Tante Lolly");
    }

    @Test
    void slashSeparator_abbreviatedFirstName() {
        assertThat(PersonNameParser.parseReceivers("Walter de Gruyter//Eugenie de Gruyter"))
                .containsExactly("Walter de Gruyter", "Eugenie de Gruyter");
    }

    @Test
    void slashSeparator_withSpacesAroundSlashes() {
        assertThat(PersonNameParser.parseReceivers(" Herbert Cram // Eugenie de Gruyter "))
                .containsExactly("Herbert Cram", "Eugenie de Gruyter");
    }

    @Test
    void slashSeparator_segmentContainsUnd() {
        assertThat(PersonNameParser.parseReceivers("Herbert und Clara Cram//Eugenie de Gruyter"))
                .containsExactly("Herbert Cram", "Clara Cram", "Eugenie de Gruyter");
    }

    @Test
    void slashSeparator_trailingSlash() {
        assertThat(PersonNameParser.parseReceivers("Herbert Cram//"))
                .containsExactly("Herbert Cram");
    }

    @Test
    void parseReceivers_returnsEmpty_whenAllPartsAreFamilie() {
        // All parts filtered out → nameParts.isEmpty() = true → return List.of()
        assertThat(PersonNameParser.parseReceivers("Familie und Familie")).isEmpty();
    }

    @Test
    void parseReceivers_singleTokenKnownLastName_notDistributed() {
        // "Müller und Herbert de Gruyter":
        //   last segment = "Herbert de Gruyter" → detectedLastName = "de Gruyter"
        //   "Müller": !contains(" ") = true BUT findKnownLastName("Müller") != null → else branch → kept as-is
        List<String> result = PersonNameParser.parseReceivers("Müller und Herbert de Gruyter");
        assertThat(result).containsExactlyInAnyOrder("Müller", "Herbert de Gruyter");
    }

    // --- pipeline pass-through methods ---

    @Test
    void stripMaidenName_isPassthrough() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Walter de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Walter de Gruyter");
        assertThat(result.maidenName()).isNull();
    }

    @Test
    void stripAnnotation_noParens_returnsNull() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("Walter de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Walter de Gruyter");
        assertThat(result.annotation()).isNull();
    }

    @Test
    void stripAnnotation_birthYear_noSpace() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("Clara de Gruyter(*1871)");
        assertThat(result.cleaned()).isEqualTo("Clara de Gruyter");
        assertThat(result.annotation()).isEqualTo("*1871");
    }

    @Test
    void stripAnnotation_uncertainty_withSpace() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("Ernst Kurmany (?)");
        assertThat(result.cleaned()).isEqualTo("Ernst Kurmany");
        assertThat(result.annotation()).isEqualTo("?");
    }

    @Test
    void stripAnnotation_nickname_noSpace() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("Gertrud D.(Tuttu)");
        assertThat(result.cleaned()).isEqualTo("Gertrud D.");
        assertThat(result.annotation()).isEqualTo("Tuttu");
    }

    @Test
    void stripAnnotation_uncertainName_extractsNameBack() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("Richard  (Quast ? )");
        assertThat(result.cleaned()).isEqualTo("Richard Quast");
        assertThat(result.annotation()).isEqualTo("?");
    }

    @Test
    void stripAnnotation_onlyParen_returnsPlaceholder() {
        PersonNameParser.AnnotationResult result = PersonNameParser.stripAnnotation("(OnlyParen)");
        assertThat(result.cleaned()).isEmpty();
        assertThat(result.annotation()).isEqualTo("OnlyParen");
    }

    // --- split — annotation extraction end-to-end ---

    @Test
    void split_birthYearAnnotation_extracted() {
        PersonNameParser.SplitName result = PersonNameParser.split("Clara de Gruyter(*1871)");
        assertThat(result.firstName()).isEqualTo("Clara");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
        assertThat(result.annotation()).isEqualTo("*1871");
    }

    @Test
    void split_uncertainName_extractsLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Richard  (Quast ? )");
        assertThat(result.firstName()).isEqualTo("Richard");
        assertThat(result.lastName()).isEqualTo("Quast");
        assertThat(result.annotation()).isEqualTo("?");
    }

    @Test
    void stripTitle_noPrefix_returnsNull() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Walter de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Walter de Gruyter");
        assertThat(result.title()).isNull();
    }

    @Test
    void stripTitle_tante() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Tante Molly");
        assertThat(result.cleaned()).isEqualTo("Molly");
        assertThat(result.title()).isEqualTo("Tante");
    }

    @Test
    void stripTitle_schwester() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Schwester Hanni");
        assertThat(result.cleaned()).isEqualTo("Hanni");
        assertThat(result.title()).isEqualTo("Schwester");
    }

    @Test
    void stripTitle_frau() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Frau Bakker");
        assertThat(result.cleaned()).isEqualTo("Bakker");
        assertThat(result.title()).isEqualTo("Frau");
    }

    @Test
    void stripTitle_cousine_withFullName() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Cousine Emmy Haniel");
        assertThat(result.cleaned()).isEqualTo("Emmy Haniel");
        assertThat(result.title()).isEqualTo("Cousine");
    }

    @Test
    void stripTitle_freifrau() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Freifrau von Massenbach");
        assertThat(result.cleaned()).isEqualTo("von Massenbach");
        assertThat(result.title()).isEqualTo("Freifrau");
    }

    @Test
    void stripTitle_dotPrefix_withSpace() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Dr. Sattelmacher");
        assertThat(result.cleaned()).isEqualTo("Sattelmacher");
        assertThat(result.title()).isEqualTo("Dr.");
    }

    @Test
    void stripTitle_dotPrefix_noSpace() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Dr.von Gelden");
        assertThat(result.cleaned()).isEqualTo("von Gelden");
        assertThat(result.title()).isEqualTo("Dr.");
    }

    @Test
    void stripTitle_stacked_profDr() {
        PersonNameParser.TitleResult result = PersonNameParser.stripTitle("Prof. Dr. Muller");
        assertThat(result.cleaned()).isEqualTo("Muller");
        assertThat(result.title()).isEqualTo("Prof. Dr.");
    }

    // --- split — title extraction end-to-end ---

    @Test
    void split_tante_setsTitle_firstNameNull() {
        PersonNameParser.SplitName result = PersonNameParser.split("Tante Molly");
        assertThat(result.title()).isEqualTo("Tante");
        assertThat(result.firstName()).isNull();
        assertThat(result.lastName()).isEqualTo("Molly");
    }

    @Test
    void split_dotTitle_afterDotNorm() {
        PersonNameParser.SplitName result = PersonNameParser.split("Dr.Fr.Zarncke");
        assertThat(result.title()).isEqualTo("Dr.");
        assertThat(result.firstName()).isEqualTo("Fr.");
        assertThat(result.lastName()).isEqualTo("Zarncke");
    }

    @Test
    void split_dotTitle_noSpace_vonLastName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Dr.von Gelden");
        assertThat(result.title()).isEqualTo("Dr.");
        assertThat(result.firstName()).isNull();
        assertThat(result.lastName()).isEqualTo("von Gelden");
    }

    // --- regression: non-prefixes not stripped ---

    @Test
    void split_walter_noTitleStrip() {
        PersonNameParser.SplitName result = PersonNameParser.split("Walter de Gruyter");
        assertThat(result.title()).isNull();
        assertThat(result.firstName()).isEqualTo("Walter");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
    }

    @Test
    void split_conrad_vonGeldern_noTitleStrip() {
        PersonNameParser.SplitName result = PersonNameParser.split("Conrad von Geldern");
        assertThat(result.title()).isNull();
        assertThat(result.firstName()).isEqualTo("Conrad");
        assertThat(result.lastName()).isEqualTo("von Geldern");
    }

    // --- stripMaidenName — maiden name extraction ---

    @Test
    void stripMaidenName_standardDot_singleWord() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Eugenie de Gruyter geb. Muller");
        assertThat(result.cleaned()).isEqualTo("Eugenie de Gruyter");
        assertThat(result.maidenName()).isEqualTo("Muller");
    }

    @Test
    void stripMaidenName_dot_multiWordMaidenName() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Clara Cram geb. de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Clara Cram");
        assertThat(result.maidenName()).isEqualTo("de Gruyter");
    }

    @Test
    void stripMaidenName_commaPrefix_noDot_multiWord() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Ella Dieckmann, geb de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Ella Dieckmann");
        assertThat(result.maidenName()).isEqualTo("de Gruyter");
    }

    @Test
    void stripMaidenName_noDot_singleWord() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Elise Rockstroh geb Sintenis");
        assertThat(result.cleaned()).isEqualTo("Elise Rockstroh");
        assertThat(result.maidenName()).isEqualTo("Sintenis");
    }

    @Test
    void stripMaidenName_noDot_noMarriedLastName() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Elisabeth geb Fernow");
        assertThat(result.cleaned()).isEqualTo("Elisabeth");
        assertThat(result.maidenName()).isEqualTo("Fernow");
    }

    @Test
    void stripMaidenName_noGeb_returnsNullMaidenName() {
        PersonNameParser.MaidenNameResult result = PersonNameParser.stripMaidenName("Walter de Gruyter");
        assertThat(result.cleaned()).isEqualTo("Walter de Gruyter");
        assertThat(result.maidenName()).isNull();
    }

    // --- split — maiden name extraction end-to-end ---

    @Test
    void split_gebDot_extractsMaidenName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Eugenie de Gruyter geb. Muller");
        assertThat(result.firstName()).isEqualTo("Eugenie");
        assertThat(result.lastName()).isEqualTo("de Gruyter");
        assertThat(result.maidenName()).isEqualTo("Muller");
    }

    @Test
    void split_gebNoDot_multiWordMaidenName() {
        PersonNameParser.SplitName result = PersonNameParser.split("Clara Cram geb. de Gruyter");
        assertThat(result.firstName()).isEqualTo("Clara");
        assertThat(result.lastName()).isEqualTo("Cram");
        assertThat(result.maidenName()).isEqualTo("de Gruyter");
    }

    // --- enum values ---

    @Test
    void personType_hasFiveValues() {
        assertThat(PersonType.values()).containsExactly(
                PersonType.PERSON, PersonType.INSTITUTION, PersonType.GROUP,
                PersonType.UNKNOWN, PersonType.SKIP);
    }

    @Test
    void personNameAliasType_includesMaidenName() {
        assertThat(PersonNameAliasType.valueOf("MAIDEN_NAME")).isEqualTo(PersonNameAliasType.MAIDEN_NAME);
    }
}
