package org.raddatz.familienarchiv.importing;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonService;
import org.raddatz.familienarchiv.person.PersonUpsertCommand;
import org.raddatz.familienarchiv.person.relationship.RelationType;
import org.raddatz.familienarchiv.person.relationship.RelationshipService;
import org.raddatz.familienarchiv.person.relationship.dto.RelationshipUpsertRequest;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class PersonTreeImporterTest {

    @Test
    void load_upsertsTreePersonBySourceRef_withFamilyMemberFlag(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_002","firstName":"Elsgard","lastName":"Allemeyer","maidenName":"Wöhler",
                   "notes":"Nichte","birthYear":1920,"deathYear":1999,"familyMember":true,"personId":"allemeyer-elsgard"}
                ],"relationships":[]}
                """);

        new PersonTreeImporter(personService, relationshipService)
                .load(json.toFile());

        ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
        verify(personService).upsertBySourceRef(captor.capture());
        PersonUpsertCommand cmd = captor.getValue();
        assertThat(cmd.sourceRef()).isEqualTo("allemeyer-elsgard");
        assertThat(cmd.familyMember()).isTrue();
        assertThat(cmd.provisional()).isFalse();
    }

    @Test
    void load_createsRelationship_resolvingRowIdsToUpsertedPersons(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        UUID idA = UUID.randomUUID();
        UUID idB = UUID.randomUUID();
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> {
            PersonUpsertCommand c = inv.getArgument(0);
            return Person.builder().id(c.sourceRef().equals("a") ? idA : idB)
                    .sourceRef(c.sourceRef()).lastName(c.lastName()).build();
        });
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"A","familyMember":true,"personId":"a"},
                  {"rowId":"row_b","lastName":"B","familyMember":true,"personId":"b"}
                ],"relationships":[
                  {"personId":"row_a","relatedPersonId":"row_b","type":"SPOUSE_OF","source":"verheiratet_mit"}
                ]}
                """);

        new PersonTreeImporter(personService, relationshipService)
                .load(json.toFile());

        ArgumentCaptor<RelationshipUpsertRequest> captor = ArgumentCaptor.forClass(RelationshipUpsertRequest.class);
        verify(relationshipService).addRelationship(eq(idA), captor.capture());
        assertThat(captor.getValue().relatedPersonId()).isEqualTo(idB);
        assertThat(captor.getValue().relationType()).isEqualTo(RelationType.SPOUSE_OF);
    }

    @Test
    void load_swallowsDuplicateRelationship_forIdempotentReimport(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any()))
                .thenAnswer(inv -> personOf(inv.getArgument(0)));
        doThrow(DomainException.conflict(ErrorCode.DUPLICATE_RELATIONSHIP, "exists"))
                .when(relationshipService).addRelationship(any(), any());
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"A","familyMember":true,"personId":"a"},
                  {"rowId":"row_b","lastName":"B","familyMember":true,"personId":"b"}
                ],"relationships":[
                  {"personId":"row_a","relatedPersonId":"row_b","type":"SPOUSE_OF","source":"verheiratet_mit"}
                ]}
                """);

        PersonTreeImporter importer = new PersonTreeImporter(personService, relationshipService);

        // Must not propagate the conflict — re-import is idempotent.
        importer.load(json.toFile());

        verify(relationshipService).addRelationship(any(), any());
    }

    @Test
    void load_propagatesUnexpectedDomainException_fromAddRelationship(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any()))
                .thenAnswer(inv -> personOf(inv.getArgument(0)));
        // An unexpected ErrorCode (not DUPLICATE/CIRCULAR) must NOT be swallowed.
        doThrow(DomainException.internal(ErrorCode.INTERNAL_ERROR, "boom"))
                .when(relationshipService).addRelationship(any(), any());
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"A","familyMember":true,"personId":"a"},
                  {"rowId":"row_b","lastName":"B","familyMember":true,"personId":"b"}
                ],"relationships":[
                  {"personId":"row_a","relatedPersonId":"row_b","type":"SPOUSE_OF","source":"verheiratet_mit"}
                ]}
                """);

        PersonTreeImporter importer = new PersonTreeImporter(personService, relationshipService);

        assertThatThrownBy(() -> importer.load(json.toFile()))
                .isInstanceOf(DomainException.class)
                .extracting("code").isEqualTo(ErrorCode.INTERNAL_ERROR);
    }

    @Test
    void load_skipsRelationship_whenRowIdUnresolved(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"A","familyMember":true,"personId":"a"}
                ],"relationships":[
                  {"personId":"row_a","relatedPersonId":"row_ghost","type":"SPOUSE_OF","source":"x"}
                ]}
                """);

        new PersonTreeImporter(personService, relationshipService)
                .load(json.toFile());

        verify(relationshipService, org.mockito.Mockito.never()).addRelationship(any(), any());
    }

    // ─── generation (#689) ─────────────────────────────────────────────────────

    @Test
    void load_passesGenerationFromJson(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"Cram","firstName":"Herbert","familyMember":true,
                   "personId":"herbert-cram","generation":3}
                ],"relationships":[]}
                """);

        new PersonTreeImporter(personService, relationshipService).load(json.toFile());

        ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
        verify(personService).upsertBySourceRef(captor.capture());
        assertThat(captor.getValue().generation()).isEqualTo(3);
    }

    @Test
    void load_returnsNullGeneration_whenAbsentFromJson(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"Cram","firstName":"Herbert","familyMember":true,
                   "personId":"herbert-cram"}
                ],"relationships":[]}
                """);

        new PersonTreeImporter(personService, relationshipService).load(json.toFile());

        ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
        verify(personService).upsertBySourceRef(captor.capture());
        assertThat(captor.getValue().generation()).isNull();
    }

    @Test
    void load_skipsOutOfRangeGeneration_logsWarn_neverAborts(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        RelationshipService relationshipService = mock(RelationshipService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path json = write(tempDir, """
                {"persons":[
                  {"rowId":"row_a","lastName":"Cram","firstName":"Herbert","familyMember":true,
                   "personId":"herbert-cram","generation":99}
                ],"relationships":[]}
                """);

        new PersonTreeImporter(personService, relationshipService).load(json.toFile());

        ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
        verify(personService).upsertBySourceRef(captor.capture());
        assertThat(captor.getValue().generation()).isNull();
    }

    private static Person personOf(PersonUpsertCommand cmd) {
        return Person.builder().id(UUID.randomUUID()).sourceRef(cmd.sourceRef()).lastName(cmd.lastName()).build();
    }

    private Path write(Path dir, String json) throws Exception {
        Path file = dir.resolve("canonical-persons-tree.json");
        Files.writeString(file, json);
        return file;
    }
}
