package ru.nbch.credit_tracker.stax;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors;
import ru.nbch.credit_tracker.entities.external_response.online_download.Person;
import ru.nbch.credit_tracker.entities.external_response.online_download.Persons;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject;
import ru.nbch.credit_tracker.service.xml.stax.*;

import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

@SpringBootTest(classes = {StaxTest.TestConfig.class})
public class StaxTest {

    private final StaxXmlProcessor staxXmlProcessor;

    @Autowired
    public StaxTest(StaxXmlProcessor staxXmlProcessor) {
        this.staxXmlProcessor = staxXmlProcessor;
    }

    @Configuration
    public static class TestConfig {
        @Bean
        public StaxXmlProcessor xmlProcessor() {
            List<Supplier<StaxReader<?>>> staxReader = new ArrayList<>();
            staxReader.add(MonitoringRequestStaxReader::new);
            staxReader.add(OnlineDownloadReportPersonsStaxReader::new);
            staxReader.add(MonitoringReportErrorsStaxtReader::new);
            return new XmlProcessorStaxImpl(staxReader);
        }
    }


    @Test
    void testValidRequest() throws IOException {
        MonitoringRequest monitoringRequest = staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/valid_request.xml").readAllBytes(), MonitoringRequest.class);
        Assertions.assertNotNull(monitoringRequest);
        Assertions.assertEquals("0001XX000001", monitoringRequest.getUserId());
        Assertions.assertEquals("test", monitoringRequest.getPassword());
        Assertions.assertEquals("MY_BATCH_170", monitoringRequest.getPackageName());
        Subject firstSubject = monitoringRequest.getSubjects().getSubject().get(0);
        Assertions.assertEquals("abc123", firstSubject.getId());
        Assertions.assertEquals("273104246", firstSubject.getClientId());
        Subject secondSubject = monitoringRequest.getSubjects().getSubject().get(1);
        Assertions.assertEquals("abc124", secondSubject.getId());
        Assertions.assertEquals("272354001", secondSubject.getClientId());
    }

    @Test
    void testInvalidXmlRequest() {
        Exception ex = null;
        try {
            staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/invalid_xml_request.xml"), MonitoringRequest.class);
        } catch (Exception e) {
            ex = e;
        }
        Assertions.assertNotNull(ex);
        Assertions.assertEquals(XMLStreamException.class, ex.getCause().getClass());
    }

    @Test
    void testEmptyXmlRequest() {
        Exception ex = null;
        try {
            staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/empty_request.xml"), MonitoringRequest.class);
        } catch (Exception e) {
            ex = e;
        }
        Assertions.assertNotNull(ex);
        Assertions.assertEquals(XMLStreamException.class, ex.getCause().getClass());
    }

    @Test
    void testNonEpectedTag() throws IOException {
        MonitoringRequest monitoringRequest = staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/non_expected_tag_request.xml"), MonitoringRequest.class);
        Assertions.assertNull(monitoringRequest.getSubjects().getSubject().get(0).getId());
    }

    @Test
    void testParseNbkiMonitoringReport() {
        List<Person> firstPersonList = new ArrayList<>();
        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    Assertions.assertEquals(1, personsData.getPersons().size());
                    firstPersonList.addAll(personsData.getPersons());
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty()
                ,
                1,
                "/Report/Persons/Person");

        List<Person> secondPersonList = new ArrayList<>();
        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    secondPersonList.addAll(personsData.getPersons());
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty(),
                3,
                "/Report/Persons/Person");

        Assertions.assertEquals(firstPersonList.size(), secondPersonList.size());
    }

    @Test
    void testParseNbkiMonitoringReportWithBounds() {
        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    Assertions.assertEquals(5, personsData.getPersons().size());
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty(),
                5,
                "/Report/Persons/Person");

        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    Assertions.assertFalse(personsData.getPersons().isEmpty());
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty(),
                6,
                "/Report/Persons/Person");

        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    Assertions.assertEquals(1, personsData.getPersons().size());
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty(),
                1,
                "/Report/Persons/Person");

        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/nbki_monitoring_report.xml"), Persons.class,
                personsData -> {
                    Assertions.assertTrue(personsData.getPersons().size() == 4 || personsData.getPersons().size() == 1);
                    personsData.getPersons().clear();
                },
                obj -> obj.getPersons().isEmpty(),
                4,
                "/Report/Persons/Person");
    }

    @Test
    void testMonitoringReportErrors() {
        List<PhoneFidMap> subjects = new ArrayList<>();
        staxXmlProcessor.read(getClass().getResourceAsStream("/monitoring_request/xml/report_errors.xml"), MonitoringReportErrors.class,
                request -> {
                    if (!request.getPersons().isEmpty()) {
                        List<PhoneFidMap> batch = request.getPersons().stream().map(p -> {
                            return PhoneFidMap.builder()
                                    .id(p.getUid())
                                    .errorText(p.getValue())
                                    .build();
                        }).toList();
                        subjects.addAll(batch);
                        request.getPersons().clear();
                    }
                },
                request -> request.getPersons().isEmpty()
                ,
                3, "/Errors/p");

        Assertions.assertEquals(4, subjects.size());

        Assertions.assertEquals(97821754, subjects.get(0).getId());
        Assertions.assertEquals("ERROR_TEXT_1", subjects.get(0).getErrorText());
        Assertions.assertEquals(97821755, subjects.get(1).getId());
        Assertions.assertEquals("ERROR_TEXT_2", subjects.get(1).getErrorText());
        Assertions.assertEquals(97821756, subjects.get(2).getId());
        Assertions.assertEquals("ERROR_TEXT_3", subjects.get(2).getErrorText());
        Assertions.assertEquals(97821757, subjects.get(3).getId());
        Assertions.assertEquals("ERROR_TEXT_4", subjects.get(3).getErrorText());
    }
}
