package org.raddatz.familienarchiv.importing;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonService;
import org.raddatz.familienarchiv.person.PersonUpsertCommand;

import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class PersonRegisterImporterTest {

    @Test
    void load_upsertsPersonBySourceRef_withProvisionalFalse(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path xlsx = writePersons(tempDir, row(
                "allemeyer-elsgard", "Allemeyer", "Elsgard", "Wöhler", "Nichte von Herbert", "False"));

        new PersonRegisterImporter(personService).load(xlsx.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.lastName()).isEqualTo("Allemeyer");
        assertThat(cmd.firstName()).isEqualTo("Elsgard");
        assertThat(cmd.maidenName()).isEqualTo("Wöhler");
        assertThat(cmd.notes()).isEqualTo("Nichte von Herbert");
        assertThat(cmd.provisional()).isFalse();
    }

    @Test
    void load_parsesCapitalisedPythonBool_True(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path xlsx = writePersons(tempDir, row(
                "noise-geschirr", "Geschirr", "", "", "", "True"));

        new PersonRegisterImporter(personService).load(xlsx.toFile());

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

    @Test
    void load_skipsRowWithBlankPersonId(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        Path xlsx = writePersons(tempDir, row("", "NoId", "", "", "", "False"));

        new PersonRegisterImporter(personService).load(xlsx.toFile());

        verify(personService, times(0)).upsertBySourceRef(any());
    }

    @Test
    void load_returnsCountOfProcessedRows(@TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path xlsx = writePersons(tempDir,
                row("a-one", "One", "A", "", "", "False"),
                row("a-two", "Two", "B", "", "", "False"));

        int processed = new PersonRegisterImporter(personService).load(xlsx.toFile());

        assertThat(processed).isEqualTo(2);
    }

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

    @ParameterizedTest
    @CsvSource(value = {
            "'G 3', 3",
            "'G3', 3",
            "'G  3', 3",
            "'3', 3",
            "'  3  ', 3",
            "'G 2  de Gruyter', 2",
            "'', null",
            "'garbage', null",
            "'G 99', null",
            "'G -1', null"
    }, nullValues = "null")
    void load_parsesGeneration_perRegex(String raw, Integer expected, @TempDir Path tempDir) throws Exception {
        PersonService personService = mock(PersonService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path xlsx = writePersonsWithGeneration(tempDir,
                rowWithGeneration("herbert-cram", "Cram", "Herbert", "", "", "False", raw));

        new PersonRegisterImporter(personService).load(xlsx.toFile());

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

    @Test
    void load_succeeds_andLeavesGenerationNull_whenArtifactHasNoGenerationColumn(@TempDir Path tempDir) throws Exception {
        // REQ-IMP-001: older artifacts without the `generation` column must still
        // import. REQUIRED_HEADERS is intentionally not extended.
        PersonService personService = mock(PersonService.class);
        when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
        Path xlsx = writePersons(tempDir, row(
                "old-artifact", "Mueller", "Hans", "", "", "False"));

        new PersonRegisterImporter(personService).load(xlsx.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())
                .firstName(cmd.firstName()).lastName(cmd.lastName())
                .provisional(cmd.provisional()).build();
    }

    private Map<String, String> row(String personId, String lastName, String firstName,
                                    String maidenName, String notes, String provisional) {
        Map<String, String> r = new LinkedHashMap<>();
        r.put("person_id", personId);
        r.put("last_name", lastName);
        r.put("first_name", firstName);
        r.put("maiden_name", maidenName);
        r.put("notes", notes);
        r.put("provisional", provisional);
        return r;
    }

    @SafeVarargs
    private Path writePersons(Path dir, Map<String, String>... rows) throws Exception {
        Path xlsx = dir.resolve("canonical-persons.xlsx");
        List<String> headers = List.of("person_id", "last_name", "first_name", "maiden_name", "notes", "provisional");
        try (XSSFWorkbook wb = new XSSFWorkbook()) {
            Sheet sheet = wb.createSheet("Sheet1");
            Row header = sheet.createRow(0);
            for (int i = 0; i < headers.size(); i++) {
                header.createCell(i).setCellValue(headers.get(i));
            }
            for (int r = 0; r < rows.length; r++) {
                Row row = sheet.createRow(r + 1);
                for (int c = 0; c < headers.size(); c++) {
                    row.createCell(c).setCellValue(rows[r].getOrDefault(headers.get(c), ""));
                }
            }
            try (OutputStream out = Files.newOutputStream(xlsx)) {
                wb.write(out);
            }
        }
        return xlsx;
    }

    private Map<String, String> rowWithGeneration(String personId, String lastName, String firstName,
                                                  String maidenName, String notes, String provisional,
                                                  String generation) {
        Map<String, String> r = row(personId, lastName, firstName, maidenName, notes, provisional);
        r.put("generation", generation);
        return r;
    }

    @SafeVarargs
    private Path writePersonsWithGeneration(Path dir, Map<String, String>... rows) throws Exception {
        Path xlsx = dir.resolve("canonical-persons.xlsx");
        List<String> headers = List.of(
                "person_id", "last_name", "first_name", "maiden_name", "notes", "provisional", "generation");
        try (XSSFWorkbook wb = new XSSFWorkbook()) {
            Sheet sheet = wb.createSheet("Sheet1");
            Row header = sheet.createRow(0);
            for (int i = 0; i < headers.size(); i++) {
                header.createCell(i).setCellValue(headers.get(i));
            }
            for (int r = 0; r < rows.length; r++) {
                Row row = sheet.createRow(r + 1);
                for (int c = 0; c < headers.size(); c++) {
                    row.createCell(c).setCellValue(rows[r].getOrDefault(headers.get(c), ""));
                }
            }
            try (OutputStream out = Files.newOutputStream(xlsx)) {
                wb.write(out);
            }
        }
        return xlsx;
    }
}
