package com.infonow.crux.dao.orm.hibernate;

import com.infonow.crux.*;
import com.infonow.crux.dao.DaoException;
import com.infonow.crux.dao.InowProfileCustomerDao;
import com.infonow.crux.dao.OrchestrationInserts;
import com.infonow.crux.dao.jdbc.mapper.InowProfileRowMapper;
import com.infonow.crux.impl.*;
import com.infonow.crux.query.PagedQueryResult;
import com.infonow.crux.svcClient.InowProfileCountryCode;
import com.infonow.crux.svcClient.InowProfileCustomerType;
import com.infonow.framework.util.cache.Cache;
import com.infonow.framework.util.cache.CacheManager;

import com.infonow.framework.util.spring.JdbcTemplateProxy;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.*;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.*;

import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
//import org.mockito.ArgumentMatchers;
//import static org.mockito.ArgumentMatchers.any;
//import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

public class InowProfileDaoTest {

    @Mock
    private JdbcTemplateProxy jdbcTemplate;

    @Mock
    private CountryDao countryDao;

    @Mock
    private InowProfileCustomerDao inowProfileCustomerDao;

    @Mock
    private OrchestrationInserts orchestrationDao;

    @InjectMocks
    private InowProfileDao inowProfileDao;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        reset();
//        inowProfileDao.setJdbcTemplate(jdbcTemplate);
//        inowProfileDao.setCountryDao(countryDao);
//        inowProfileDao.setInowProfileCustomerDao(inowProfileCustomerDao);
//        inowProfileDao.setOrchestrationDao(orchestrationDao);
    }

    @Test
    public void testNextId() {
        System.out.println(jdbcTemplate);
        when(jdbcTemplate.queryForObject(anyString(), eq(Long.class))).thenReturn(1L);
        Long nextId = inowProfileDao.nextId();
        assertNotNull(nextId);
        assertEquals((Long)1L, nextId);
    }

    @Test
    public void testCreateIdentifier() {
        String identifier = inowProfileDao.createIdentifier(1L);
        assertNotNull(identifier);
        assertEquals("INOW-00000000000001", identifier);
    }

    @Test
    public void testGetInowProfiles() throws DaoException
    {
        List<String> identifiers = Arrays.asList("INOW-12345");
        String customerId = "customer1";
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setIdentifier("INOW-12345");
        List<InowProfile> inowProfiles = Arrays.asList(inowProfile);
        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        Map<String, InowProfile> result = inowProfileDao.getInowProfiles(identifiers, customerId);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(inowProfile, result.get("INOW-12345"));
    }

    @Test
    public void testGetBySid() {
        Long sid = 1L;
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setSid(sid);
        List<InowProfile> inowProfiles = Arrays.asList(inowProfile);
        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        InowProfile result = inowProfileDao.getBySid(sid);
        assertNotNull(result);
        assertEquals(inowProfile, result);
    }

    @Test
    public void testFindById() {
        String id = "INOW-12345";
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setIdentifier(id);
        List<InowProfile> inowProfiles = Arrays.asList(inowProfile);
        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        InowProfile result = inowProfileDao.findById(id);
        assertNotNull(result);
        assertEquals(inowProfile, result);
    }

    @Test
    public void testFindWhereIndividualIsNull() {
        com.infonow.crux.dao.InowProfileDao.InowProfileResultHandler resultHandler = mock(
                com.infonow.crux.dao.InowProfileDao.InowProfileResultHandler.class);
        doAnswer(invocation -> {
            ResultSet rs = mock(ResultSet.class);
            when(rs.getString("ENTITY_NAME")).thenReturn("Test Entity");
            when(rs.getLong("SID")).thenReturn(1L);
            when(rs.getLong("CLASSIFICATION_TYPE_SID")).thenReturn(1L);
            InowProfileRowMapper mapper = new InowProfileRowMapper();
            InowProfile inowProfile = (InowProfile) mapper.mapRow(rs, 1);
            resultHandler.onResult(inowProfile);
            return null;
        }).when(jdbcTemplate).query(anyString(), any(RowCallbackHandler.class));

        inowProfileDao.findWhereIndividualIsNull(resultHandler);
        verify(resultHandler, times(1)).onResult(any(InowProfile.class));
    }

    @Test
    public void testUpdateIndividualAndClassification() {
        List<InowProfile> inowProfiles = new ArrayList<>();
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setSid(1L);
        inowProfiles.add(inowProfile);

        inowProfileDao.updateIndividualAndClassification(inowProfiles);
        verify(jdbcTemplate, times(1)).batchUpdate(anyString(), any(BatchPreparedStatementSetter.class));
    }

    @Test
    public void testGetRandomInowProfile() {
        InowProfile inowProfile = new InowProfileImpl();
        List<InowProfile> inowProfiles = Arrays.asList(inowProfile);
        when(jdbcTemplate.query(anyString(), any(RowMapper.class))).thenReturn(inowProfiles);

        InowProfile result = inowProfileDao.getRandomInowProfile();
        assertNotNull(result);
        assertEquals(inowProfile, result);
    }

    @Test
    public void testGetRecordCountForSearch() throws DaoException {
        when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(1);
        int count = inowProfileDao.getRecordCountForSearch("id", "name", "street1", "street2", "city", "state", "postal", "country", false);
        assertNotNull(count);
        assertEquals(1, count);
    }

    @Test
    public void testFindByWildcard() throws DaoException {
        List<InowProfile> inowProfiles = new ArrayList<>();
        InowProfile inowProfile = new InowProfileImpl();
        inowProfiles.add(inowProfile);
        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        List<InowProfile> result = inowProfileDao.findByWildcard("id", "name", "street1", "street2", "city", "state", "postal", "country", false, 0, 10);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(inowProfile, result.get(0));
    }

    @Test
    public void testUpdateCountries() throws DaoException {
        List<InowProfileCountryCode> inowProfileCountryCodes = new ArrayList<>();
        InowProfileCountryCode inowProfileCountryCode = new InowProfileCountryCode();
        inowProfileCountryCodes.add(inowProfileCountryCode);

        PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
        TransactionStatus transactionStatus = mock(TransactionStatus.class);
        when(transactionManager.getTransaction(any(DefaultTransactionDefinition.class))).thenReturn(transactionStatus);

        inowProfileDao.updateCountries(inowProfileCountryCodes);
        verify(jdbcTemplate, times(3)).batchUpdate(anyString(), any(BatchPreparedStatementSetter.class));
        verify(transactionManager, times(1)).commit(transactionStatus);
    }

    @Test
    public void testUpdateGrade() throws DaoException {
        doNothing().when(jdbcTemplate).update(anyString(), any(Map.class));
        inowProfileDao.updateGrade(1L, 1L);
        verify(jdbcTemplate, times(1)).update(anyString(), any(Map.class));
    }

    @Test
    public void testUpdateGradeAndIsValidFields() throws DaoException {
        doNothing().when(jdbcTemplate).update(anyString(), any(Object[].class));
        inowProfileDao.updateGradeAndIsValidFields(1L, 1L, true, true, true);
        verify(jdbcTemplate, times(1)).update(anyString(), any(Object[].class));
    }

    @Test
    public void testUpdateAddressFields() {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setSid(1L);
        inowProfile.setName("Test Name");
        inowProfile.setStreet1("Street 1");
        inowProfile.setStreet2("Street 2");
        inowProfile.setCity("City");
        inowProfile.setStateProvince("State");
        inowProfile.setStateProvinceCode("SPC");
        inowProfile.setPostalCode("Postal");
        inowProfile.setValidPostalCode(true);
        inowProfile.setValidStateProvince(true);
        inowProfile.setValidCity(true);
        inowProfile.setCountry("Country");
        inowProfile.setCountryObject(new CountryImpl());

        when(jdbcTemplate.update(anyString(), any(Object[].class))).thenReturn(1);
        int result = inowProfileDao.updateAddressFields(inowProfile);
        assertEquals(1, result);
    }

    @Test
    public void testFindUnclassified() throws DaoException {
        List<Customer> customers = new ArrayList<>();
        Customer customer = new CustomerImpl();
        customers.add(customer);

        List<InowProfile> inowProfiles = new ArrayList<>();
        InowProfile inowProfile = new InowProfileImpl();
        inowProfiles.add(inowProfile);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        PagedQueryResult<InowProfile> result = inowProfileDao.findUnclassified(customers, 0, 10, "sort", true);
        assertNotNull(result);
        assertEquals(1, result.getTotalCount());
        //assertEquals(inowProfile, result.getResults().get(0));
    }

    @Test
    public void testGetClassifiedEntityCount() throws DaoException {
        when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(1);
        int count = inowProfileDao.getClassifiedEntityCount();
        assertNotNull(count);
        assertEquals(1, count);
    }

    @Test
    public void testGetClassifiedEntityCountByCode() throws DaoException {
        when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), any(Object[].class))).thenReturn(1);
        Integer count = inowProfileDao.getClassifiedEntityCount("code");
        assertNotNull(count);
        assertEquals((Integer)1, count);
    }

    @Test
    public void testGetClassifications() throws DaoException {
        List<ClassificationType> classificationTypes = new ArrayList<>();
        ClassificationType classificationType = new ClassificationTypeImpl();
        classificationTypes.add(classificationType);

        when(jdbcTemplate.query(anyString(), any(RowMapper.class))).thenReturn(classificationTypes);

        List<ClassificationType> result = inowProfileDao.getClassifications();
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(classificationType, result.get(0));
    }

    @Test
    public void testGetClassificationType() throws DaoException {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setSid(1L);

        ClassificationType classificationType = new ClassificationTypeImpl();
        List<ClassificationType> classificationTypes = Arrays.asList(classificationType);

        System.out.println(jdbcTemplate);
        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(classificationTypes);

        ClassificationType result = inowProfileDao.getClassificationType(inowProfile);
        assertNotNull(result);
        assertEquals(classificationType, result);
    }

    @Test
    public void testGetClassificationTypeByCode() throws DaoException {
        ClassificationType classificationType = new ClassificationTypeImpl();
        List<ClassificationType> classificationTypes = Arrays.asList(classificationType);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(classificationTypes);

        ClassificationType result = inowProfileDao.getClassificationTypeByCode("code");
        assertNotNull(result);
        assertEquals(classificationType, result);
    }

    @Test
    public void testFindClassified() throws DaoException {
        List<InowProfile> inowProfiles = new ArrayList<>();
        InowProfile inowProfile = new InowProfileImpl();
        inowProfiles.add(inowProfile);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        List<InowProfile> result = inowProfileDao.findClassified(0, 10, "sort", true);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(inowProfile, result.get(0));
    }

    @Test
    public void testClassify() throws DaoException {
        List<InowProfileCustomerType> customerTypes = new ArrayList<>();
        InowProfileCustomerType customerType = new InowProfileCustomerType();
        customerType.setInowProfileSid(1L);
        customerType.setCustomerType("type");
        customerTypes.add(customerType);

        when(jdbcTemplate.queryForObject(anyString(), any(Object[].class), eq(String.class))).thenReturn("type");

        List<Long> result = inowProfileDao.classify(customerTypes);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals((Long)1L, result.get(0));
    }

    @Test
    public void testClearClassifications() {
        List<Long> inowProfileSids = Arrays.asList(1L, 2L, 3L);

        doNothing().when(jdbcTemplate).update(anyString(), any(Object[].class));

        inowProfileDao.clearClassifications(inowProfileSids);
        verify(jdbcTemplate, times(1)).update(anyString(), any(Object[].class));
    }

    @Test
    public void testSaveR2rReporter() throws DaoException {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setSid(1L);

        doNothing().when(jdbcTemplate).update(anyString(), any(Object[].class));

        inowProfileDao.saveR2rReporter(inowProfile, "value");
        verify(jdbcTemplate, times(1)).update(anyString(), any(Object[].class));
    }

    @Test
    public void testGetBySalesLineItemAndAddressType() throws DaoException {
        InowProfile inowProfile = new InowProfileImpl();
        List<InowProfile> inowProfiles = Arrays.asList(inowProfile);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        InowProfile result = inowProfileDao.getBySalesLineItemAndAddressType("customerId", 1L, "addressType");
        assertNotNull(result);
        assertEquals(inowProfile, result);
    }

    @Test
    public void testDeleteDuplicate() throws DaoException {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setIdentifier("INOW-12345");

        doNothing().when(jdbcTemplate).update(anyString(), any(Map.class));

        inowProfileDao.deleteDuplicate(inowProfile);
        verify(jdbcTemplate, times(1)).update(anyString(), any(Map.class));
    }

    @Test
    public void testSaveDuplicate() throws DaoException {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setIdentifier("INOW-12345");
        InowProfile duplicateInowProfile = new InowProfileImpl();
        duplicateInowProfile.setIdentifier("INOW-54321");

        Map<InowProfile, List<InowProfile>> inowProfileDuplicateMap = new HashMap<>();
        List<InowProfile> duplicates = new ArrayList<>();
        duplicates.add(duplicateInowProfile);
        inowProfileDuplicateMap.put(inowProfile, duplicates);

        doNothing().when(jdbcTemplate).batchUpdate(anyString(), any(BatchPreparedStatementSetter.class));

        inowProfileDao.saveDuplicate(inowProfileDuplicateMap, true);
        verify(jdbcTemplate, times(1)).batchUpdate(anyString(), any(BatchPreparedStatementSetter.class));
    }

    @Test
    public void testGetCount() throws DaoException {
        when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(1);
        Integer count = inowProfileDao.getCount();
        assertNotNull(count);
        assertEquals((Long) 1L, count);
    }

    @Test
    public void testGetRecords() throws DaoException {
        List<InowProfile> inowProfiles = new ArrayList<>();
        InowProfile inowProfile = new InowProfileImpl();
        inowProfiles.add(inowProfile);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        List<InowProfile> result = inowProfileDao.getRecords(0, 10);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(inowProfile, result.get(0));
    }

    @Test
    public void testFindByFields() {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setName("Test Name");
        inowProfile.setStreet1("Street 1");
        inowProfile.setStreet2("Street 2");
        inowProfile.setCity("City");
        inowProfile.setStateProvince("State");
        inowProfile.setPostalCode("Postal");
        inowProfile.setCountry("Country");

        when(jdbcTemplate.queryForList(anyString(), anyMap(), eq(String.class))).thenReturn(Collections.singletonList(Collections.singletonMap("identifier", "INOW-12345")));

        String result = inowProfileDao.findByFields(inowProfile, true);
        assertNotNull(result);
        assertEquals("INOW-12345", result);
    }

    @Test
    public void testFindExactAddressDupes() {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setName("Test Name");
        inowProfile.setStreet1("Street 1");
        inowProfile.setStreet2("Street 2");
        inowProfile.setCity("City");
        inowProfile.setStateProvince("State");
        inowProfile.setPostalCode("Postal");
        inowProfile.setCountry("Country");

        List<InowProfile> inowProfiles = new ArrayList<>();
        inowProfiles.add(inowProfile);

        when(jdbcTemplate.query(anyString(), any(Object[].class), any(RowMapper.class))).thenReturn(inowProfiles);

        List<InowProfile> result = inowProfileDao.findExactAddressDupes(inowProfile);
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals(inowProfile, result.get(0));
    }

    @Test
    public void testFindByFieldsWithNullResults() {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setName("Test Name");
        inowProfile.setStreet1("Street 1");
        inowProfile.setStreet2("Street 2");
        inowProfile.setCity("City");
        inowProfile.setStateProvince("State");
        inowProfile.setPostalCode("Postal");
        inowProfile.setCountry("Country");

        when(jdbcTemplate.queryForList(anyString(), any(Map.class), eq(String.class))).thenReturn(Collections.emptyList());

        String result = inowProfileDao.findByFields(inowProfile, true);
        assertNull(result);
    }

    @Test
    public void testFindByFieldsWithException() {
        InowProfile inowProfile = new InowProfileImpl();
        inowProfile.setName("Test Name");
        inowProfile.setStreet1("Street 1");
        inowProfile.setStreet2("Street 2");
        inowProfile.setCity("City");
        inowProfile.setStateProvince("State");
        inowProfile.setPostalCode("Postal");
        inowProfile.setCountry("Country");

        when(jdbcTemplate.queryForList(anyString(), any(Map.class), eq(String.class))).thenThrow(new RuntimeException("Database error"));

        assertThrows(DaoException.class, () -> inowProfileDao.findByFields(inowProfile, true));
    }
}