import java.nio.charset.CharsetEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.UUID
import java.util.Map.Entry

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.profile.BaseProfile
import com.gridnine.xtrip.common.model.profile.Card
import com.gridnine.xtrip.common.model.profile.CardIndex
import com.gridnine.xtrip.common.model.profile.Contract
import com.gridnine.xtrip.common.model.profile.ContractCustomerIndex
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.model.profile.OrganizationType
import com.gridnine.xtrip.common.model.profile.Person
import com.gridnine.xtrip.common.model.profile.PersonEmployment
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.search.SortOrder
import com.gridnine.xtrip.common.util.MiscUtil.Pair
import com.gridnine.xtrip.common.util.TextUtil

Logger logger = LoggerFactory.getLogger('groovy-script')

def ownerByUid = { CardIndex index ->

    throw new Exception()

    def uid = ''
    
    EntityReference<Organization> organization = EntityStorage.get().actualize(new EntityReference<Organization>(uid, Organization.class, null))
    
    return organization        
}
    
def ownerByParent = { CardIndex index ->
    
    EntityReference<Organization> organization = null

    if (Person.class.isAssignableFrom(index.getSource().getType())) {

        EntityContainer<Person> personContainer = EntityStorage.get().resolve((EntityReference<Person>) index.getSource())

        if(personContainer != null) {

            Person person = personContainer.getEntity()

            for(PersonEmployment employment : person.getEmployments()) {

                if(employment.getOrganization() != null && (organization == null || employment.isMainEmployment())) {
                    organization = employment.getOrganization()
                }
            }
            
        } else {
            logger.info("CIR: unable to load person " + index.getSource() + "(" + index.getSource().getUid() + ")")
        }
        
    } else if(Organization.class.isAssignableFrom(index.getSource().getType())) {
        organization = (EntityReference<Organization>) index.getSource()
    }

    if(organization != null) {
        
        logger.info("CIR: found organization " + organization + " for " + index.getSource() + " (" + index.getSource().getUid() + ")")
        
        List<String> uids = new ArrayList<>()
        
        while(true) {

            if(uids.contains(organization.getUid())) {

                logger.info("CIR: parent loop found for " + index.getSource() + "(" + index.getSource().getUid() + ")")
                break
            }

            uids.add(organization.getUid())

            EntityContainer<Organization> organizationContainer = EntityStorage.get().resolve(organization)

            if(organizationContainer != null && !organizationContainer.getEntity().getTypes().contains(OrganizationType.AGENCY) && organizationContainer.getEntity().getParent() != null) {
                organization = organizationContainer.getEntity().getParent()
            } else {
                break
            }
        }

    } else {
        logger.info("CIR: unable to define organization for " + index.getSource() + " (" + index.getSource().getUid() + ")")
    }

    return organization
}

def ownerByContract = { CardIndex index ->
    
    EntityReference<Organization> organization = null

    if (Person.class.isAssignableFrom(index.getSource().getType())) {

        EntityContainer<Person> personContainer = EntityStorage.get().resolve((EntityReference<Person>) index.getSource())

        if(personContainer != null) {

            Person person = personContainer.getEntity()

            for(PersonEmployment employment : person.getEmployments()) {

                if(employment.getOrganization() != null && (organization == null || employment.isMainEmployment())) {
                    organization = employment.getOrganization()
                }
            }
            
        } else {
            logger.info("CIR: unable to load person " + index.getSource() + " (" + index.getSource().getUid() + ")")
        }
        
    } else if(Organization.class.isAssignableFrom(index.getSource().getType())) {
        organization = (EntityReference<Organization>) index.getSource()
    }

    if(organization != null) {
        
        logger.info("CIR: found organization " + organization + " for " + index.getSource() + " (" + index.getSource().getUid() + ")")

        EntityContainer<Organization> organizationContainer = EntityStorage.get().resolve(organization)

        if(organizationContainer != null && organizationContainer.getEntity().getTypes().contains(OrganizationType.CORPORATE_CLIENT)) {

            logger.info("CIR: organization is client")

            query = new SearchQuery()

            query.getCriteria().getCriterions().add(SearchCriterion.eq(ContractCustomerIndex.Property.customer.name(), organization))
            query.getCriteria().getCriterions().add(SearchCriterion.eq(ContractCustomerIndex.Property.contractType.name(), ContractType.CLIENT))
            query.getCriteria().getCriterions().add(SearchCriterion.ne(ContractCustomerIndex.Property.supplier.name(), null))

            query.getCriteria().getOrders().put("containerUid", SortOrder.ASC)

            query.getPreferredProperties().add("containerUid")

            List<ContractCustomerIndex> contractIndexes = EntityStorage.get().search(ContractCustomerIndex.class, query).getData()

            logger.info("CIR: found contracts " + contractIndexes.size())

            for(ContractCustomerIndex contractIndex : contractIndexes) {

                EntityContainer<Contract> contractContainer = EntityStorage.get().resolve(contractIndex.getSource())

                if(contractContainer != null) {

                    Contract contract = contractContainer.getEntity()

                    if(contract.getSupplier() != null) {

                        organization = contract.getSupplier()

                        logger.info("CIR: found contract supplier " + organization)

                        break
                    }
                }
            }
        }
        
    } else {
        logger.info("CIR: unable to define organization for " + index.getSource() + " (" + index.getSource().getUid() + ")")
    }

    return organization
}

logger.info("CIR: file export started")

int processed = 0
int loops = 0

int included = 0
int skipped = 0

Date date = new Date()
DateFormat format = new SimpleDateFormat("MMyy")

Map<EntityReference<Organization>, Pair<List<String>, List<String>>> lines = new HashMap()

try {

    SearchQuery query = new SearchQuery()

    query.getCriteria().getOrders().put("containerUid", SortOrder.ASC)

    query.getPreferredProperties().add("containerUid")

    List<CardIndex> indexes = EntityStorage.get().search(CardIndex.class, query).getData()

    logger.info("CIR: found " + indexes.size() + " cards")

    for(CardIndex index : indexes) {

        logger.info("CIR: processing " + processed + " | " + index.getSource().getUid())
        
        EntityReference<Organization> owner = ownerByUid(index)

        if (Organization.class.isAssignableFrom(index.getSource().getType())) {
            
            EntityContainer<Organization> organizationContainer = EntityStorage.get().resolve((EntityReference<Organization>) index.getSource())

            if(organizationContainer != null) {

                Organization organization = organizationContainer.getEntity()

                Pair<List<String>, List<String>> ownerLines = lines.get(owner)
                
                if(ownerLines == null) {
                    
                    ownerLines = new Pair(new ArrayList(), new ArrayList())
                    lines.put(owner, ownerLines)
                }
                
                for(Card card : organization.getCards()) {

                    if(TextUtil.isSame(card.getUid(), index.getNavigationKey())) {

                        String sof = String.valueOf(card.isSof())
                        String bta = String.valueOf(card.isBta())
                        String remarks = !TextUtil.isBlank(card.getRemarks()) ? card.getRemarks() : '' 
                        String profileType = Organization.class.getName()
                        String profileUid = index.getSource().getUid()

                        if(!TextUtil.isBlank(card.getNameOnCard()) && card.getExpiration() != null && card.getExpiration().after(date) && card.getVendor() != null &&!TextUtil.isBlank(card.getNumber()) && !TextUtil.isBlank(card.getUid())) {

                            String cardholderName = card.getNameOnCard().toUpperCase()
                            String expirationDate = format.format(card.getExpiration())
                            String type = card.getVendor().name()
                            String pan = card.getNumber().replaceAll('[\\D]', '')
                            String uid = card.getUid()

                            ownerLines.getFirst().add(String.format('%s;%s;%s;%s;%s;%s;%s;%s;%s;%s', cardholderName, expirationDate, type, pan, sof, bta, remarks, uid, profileType, profileUid))
                            included++
                            
                        } else {

                            String cardholderName = !TextUtil.isBlank(card.getNameOnCard()) ? card.getNameOnCard().toUpperCase() : ''
                            String expirationDate = card.getExpiration() != null ? format.format(card.getExpiration()) : ''
                            String type = card.getVendor() != null ? card.getVendor().name() : ''
                            String pan = !TextUtil.isBlank(card.getNumber()) ? card.getNumber().replaceAll('[\\D]', '') : ''
                            String uid = !TextUtil.isBlank(card.getUid()) ? card.getUid() : ''

                            ownerLines.getSecond().add(String.format('%s;%s;%s;%s;%s;%s;%s;%s;%s;%s', cardholderName, expirationDate, type, pan, sof, bta, remarks, uid, profileType, profileUid))
                            
                            logger.info("CIR: invalid card of organization " + index.getSource() + " (" + index.getSource().getUid() + ")")
                            skipped++
                        }
                    }
                }
                
            } else {
                logger.info("CIR: unable to load organization " + index.getSource() + " (" + index.getSource().getUid() + ")")
            }
            
        } else if (Person.class.isAssignableFrom(index.getSource().getType())) {

            EntityContainer<Person> personContainer = EntityStorage.get().resolve((EntityReference<Person>) index.getSource())

            if(personContainer != null) {

                Person person = personContainer.getEntity()

                Pair<List<String>, List<String>> ownerLines = lines.get(owner)
                
                if(ownerLines == null) {
                    
                    ownerLines = new Pair(new ArrayList(), new ArrayList())
                    lines.put(owner, ownerLines)
                }

                for(Card card : person.getCards()) {

                    if(TextUtil.isSame(card.getUid(), index.getNavigationKey())) {

                        String sof = String.valueOf(card.isSof())
                        String bta = String.valueOf(card.isBta())
                        String remarks = !TextUtil.isBlank(card.getRemarks()) ? card.getRemarks() : '' 
                        String profileType = Person.class.getName()
                        String profileUid = index.getSource().getUid()

                        if(!TextUtil.isBlank(card.getNameOnCard()) && card.getExpiration() != null && card.getExpiration().after(date) && card.getVendor() != null && !TextUtil.isBlank(card.getNumber()) && !TextUtil.isBlank(card.getUid())) {

                            String cardholderName = card.getNameOnCard().toUpperCase()
                            String expirationDate = format.format(card.getExpiration())
                            String type = card.getVendor().name()
                            String pan = card.getNumber().replaceAll('[\\D]', '')
                            String uid = card.getUid()

                            ownerLines.getFirst().add(String.format('%s;%s;%s;%s;%s;%s;%s;%s;%s;%s', cardholderName, expirationDate, type, pan, sof, bta, remarks, uid, profileType, profileUid))
                            included++
                            
                        } else {

                            String cardholderName = !TextUtil.isBlank(card.getNameOnCard()) ? card.getNameOnCard().toUpperCase() : ''
                            String expirationDate = card.getExpiration() != null ? format.format(card.getExpiration()) : ''
                            String type = card.getVendor() != null ? card.getVendor().name() : ''
                            String pan = !TextUtil.isBlank(card.getNumber()) ? card.getNumber().replaceAll('[\\D]', '') : ''
                            String uid = !TextUtil.isBlank(card.getUid()) ? card.getUid() : ''

                            ownerLines.getSecond().add(String.format('%s;%s;%s;%s;%s;%s;%s;%s;%s;%s', cardholderName, expirationDate, type, pan, sof, bta, remarks, uid, profileType, profileUid))
                            
                            logger.info("CIR: invalid card of person " + index.getSource() + " (" + index.getSource().getUid() + ")")
                            skipped++
                        }
                    }
                }
                
            } else {
                logger.info("CIR: unable to load person " + index.getSource() + " (" + index.getSource().getUid() + ")")
            }
            
        } else {
            logger.info("CIR: unsupported profile type " + index.getSource() + " (" + index.getSource().getUid() + ")")
        }

        processed++
    }

    for(Entry<EntityReference<Organization>, Pair<List<String>, List<String>>> entry : lines.entrySet()) {
        
        String name = entry.getKey() != null ? String.format('%s_%s', !TextUtil.isBlank(entry.getKey().getCaption()) ? entry.getKey().getCaption().toLowerCase() : '_', !TextUtil.isBlank(entry.getKey().getUid()) ? entry.getKey().getUid() : '_') : '_'
        
        Files.write(Files.createDirectories(Environment.getDataFolder().toPath().resolve('export/cards')).resolve(name + '_cards.csv'), entry.getValue().getFirst(), StandardCharsets.UTF_8)
        Files.write(Files.createDirectories(Environment.getDataFolder().toPath().resolve('export/cards')).resolve(name + '_skipped.csv'), entry.getValue().getSecond(), StandardCharsets.UTF_8)
    }
    
} catch(Throwable t) {
    logger.error('', t)
}

logger.info("=================================")

logger.info("CIR: processed " + processed)

for(Entry<EntityReference<Organization>, Pair<List<String>, List<String>>> entry : lines.entrySet()) {
    logger.info("CIR: " + entry.getKey() + " -> " + entry.getValue().getFirst().size() + " | " + entry.getValue().getSecond().size())
}

logger.info("CIR: file export finished")
