import com.gridnine.bof.midoffice.ibus.IBusMidofficeContextKeys
import com.gridnine.xtrip.common.gds.model.BaseGdsAccount
import com.gridnine.xtrip.common.gds.rules.Actions
import com.gridnine.xtrip.common.gds.rules.FilteringCategories
import com.gridnine.xtrip.common.gds.rules.GdsPropertiesProvider
import com.gridnine.xtrip.common.gds.rules.Targets
import com.gridnine.xtrip.common.gds.sabre.helpers.SabreHelper
import com.gridnine.xtrip.common.gds.sabre.model.SabreGdsAccount
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.dict.Airline
import com.gridnine.xtrip.common.model.dict.CodeSystem
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.model.dict.DictionaryReference
import com.gridnine.xtrip.common.model.dict.GdsName
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.gds.GdsAccountIndex
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.model.profile.OrganizationIndex
import com.gridnine.xtrip.common.model.profile.SalesPoint
import com.gridnine.xtrip.common.model.profile.SalesPointIndex
import com.gridnine.xtrip.common.model.rules.standard.GdsNamePropertiesProvider
import com.gridnine.xtrip.common.model.rules.standard.Properties
import com.gridnine.xtrip.common.rules.RuleRestriction
import com.gridnine.xtrip.common.rules.RuleSet
import com.gridnine.xtrip.common.rules.RuleSettings
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.util.ValueHolder
import com.gridnine.xtrip.server.gds.sabre.SabreServerHelper
import com.gridnine.xtrip.server.ibus.components.MessageContext

def ls = '<br>'

List<SabreGdsAccountWrapper> wrappers = getWrappers()
SearchQuery sq = buildSearchQuery()
StringBuilder sb = new StringBuilder()
StringBuilder sbInfo = new StringBuilder()
for (def index : EntityStorage.get().search(getIndexClass(), sq).getData()) {
    EntityContainer ctr = EntityStorage.get().resolve(index.getSource())
    def entity = ctr.getEntity()
    Date now = new Date()
    RuleSet ruleSet = getRuleSet(entity)
    if (ruleSet != null && ruleSet.getRules() != null) {
        for (int i = 0; i < ruleSet.getRules().size(); i++) {
            RuleSettings rule = ruleSet.getRules().get(i)
            if (rule.getTarget() != Targets.GDS_PARAMETERS_EX) {
                continue
            }
            if (rule.getFilteringCategory() != FilteringCategories.TICKETING_SETTINGS) {
                continue
            }
            if (rule.getPeriodEnd() != null && rule.getPeriodEnd().before(now)) {
                continue
            }
            String blankOwner = getBlankOwner(rule)
            if (blankOwner == null) {
                continue
            }
            EntityContainer<Organization> blankOwnerCtr = EntityStorage.get().find(OrganizationIndex.class, OrganizationIndex.Property.code.name(), blankOwner)
            if (blankOwnerCtr == null) {
                sb.append("${ctr.entity.code},${i+1},invalid blank owner ${blankOwner}${ls}")
                continue
            }
            List<String> applicableCountries = getApplicableCountries(rule, wrappers)
            if (applicableCountries.isEmpty()) {
                continue
            }
            ValueHolder<String> errorHolder = new ValueHolder<>()
            List<String> printerTypes = applicableCountries.collect { getPrinterType(it, blankOwnerCtr.entity, errorHolder) }.unique()
            if (printerTypes.size() > 1) {
                sb.append("${ctr.entity.code},${i+1},ambiguous printer type ${String.join(", ", printerTypes)}${ls}")
                continue
            }
            String printerType = printerTypes.iterator().next()
            if (printerType == null) {
                sb.append("${ctr.entity.code},${i+1},failed to determine printer type: ${errorHolder.value}${ls}")
                continue
            }
            sbInfo.append("${ctr.entity.code},${i+1},${printerType}${ls}")
        }
    }
}
if (sbInfo.length() > 0) {
    message(sbInfo.toString())
}
if (sb.length() > 0) {
    error(sb.toString())
}

private static String getBlankOwner(RuleSettings rule) {
    return (String) rule.results.find { it.action == Actions.BLANK_OWNER }?.value
}

private static List<String> getApplicableCountries(RuleSettings rule, List<SabreGdsAccountWrapper> wrappers) {
    List<RuleRestriction> restrictions = rule.restrictions.findAll { it.property == Properties.GDS_NAME || it.property == com.gridnine.xtrip.common.gds.rules.Properties.GDS_ACCOUNT }
    return wrappers.findAll { w -> restrictions.every {it.property.match(w, it.condition, it.value) } }.collect { it.country }.unique()
}

private static String getPrinterType(String country, Organization blankOwner, ValueHolder<String> errorHolder) {
    switch (country) {
        case "RU":
            switch (blankOwner.getCode()) {
                case "BSP":
                    return "RU"
                case "Ш1":
                    return "1T"
                default:
                    switch (getAirlineCode(blankOwner.getAirline())) {
                        case "SU":
                            return "1R"
                        case "S7":
                        case "DV":
                        case "U6":
                        case "B2":
                            return "1Y"
                        case "BSP":
                            return "RU"
                        default:
                            errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                            return null
                    }
            }
        case "AM":
            switch (getAirlineCode(blankOwner.getAirline())) {
                case "DV":
                case "AF":
                case "OS":
                case "DL":
                case "KL":
                case "U6":
                    return "1Y"
                case "SU":
                case "B2":
                    return "1R"
                default:
                    errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                    return null
            }
        case "KZ":
            switch (blankOwner.getCode()) {
                case "BSP":
                case "КГА":
                    return "KZ"
                default:
                    switch (getAirlineCode(blankOwner.getAirline())) {
                        case "B2":
                        case "DV":
                        case "Z9":
                            return "1Y"
                        case "BSP":
                        case "КГА":
                            return "KZ"
                        default:
                            errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                            return null
                    }
            }
        case "UA":
        case "GE":
        case "BG":
        case "DE":
            switch (blankOwner.getCode()) {
                case "BSP":
                    return "AT"
                default:
                    switch (getAirlineCode(blankOwner.getAirline())) {
                        case "BSP":
                            return "AT"
                        default:
                            errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                            return null
                    }
            }
        case "IL":
            switch (blankOwner.getCode()) {
                case "BSP":
                    return "IL"
                default:
                    switch (getAirlineCode(blankOwner.getAirline())) {
                        case "BSP":
                            return "IL"
                        default:
                            errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                            return null
                    }
            }
        case "AZ":
            switch (blankOwner.getCode()) {
                case "BSP":
                    return "AT"
                default:
                    switch (getAirlineCode(blankOwner.getAirline())) {
                        case "J2":
                            return "1Y"
                        case "BSP":
                            return "AT"
                        default:
                            errorHolder.setValue("unexpected blank owner " + blankOwner.getCode())
                            return null
                    }
            }
        default:
            errorHolder.setValue("unexpected country " + country)
            return null
    }
}

private static String getAirlineCode(final DictionaryReference<Airline> airlineRef) {
    Airline airline = DictionaryCache.get().resolveReference(airlineRef)
    if (airline.getCodeVariants().containsKey(CodeSystem.PSEUDOBLANK.name())) {
        return airline.getCodeVariants().get(CodeSystem.PSEUDOBLANK.name())
    }
    return SabreHelper.getNativeCode(airlineRef);
}


class SabreGdsAccountWrapper implements GdsPropertiesProvider, GdsNamePropertiesProvider {

    private final EntityReference<SabreGdsAccount> account

    private final String country

    SabreGdsAccountWrapper(final EntityReference<? extends BaseGdsAccount> account) {
        this.account = (EntityReference)account
        MessageContext ctx = new MessageContext()
        ctx.putObject(IBusMidofficeContextKeys.GDS_ACCOUNT, EntityStorage.get().resolve(this.account))
        this.country = SabreServerHelper.getCountry(ctx)
    }

    @Override
    EntityReference<SabreGdsAccount> getGdsAccount() {
        return account
    }

    @Override
    String getGdsName() {
        return GdsName.SABRE.name()
    }

    String getCountry() {
        return country
    }

    @Override
    boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false
        SabreGdsAccountWrapper that = (SabreGdsAccountWrapper) o
        return Objects.equals(account, that.account)
    }

    @Override
    int hashCode() {
        return Objects.hashCode(account)
    }
}

private static List<SabreGdsAccountWrapper> getWrappers() {
    SearchQuery sq = new SearchQuery()
    sq.getCriteria().getCriterions().add(SearchCriterion.eq(GdsAccountIndex.Property.gds.name(), GdsName.SABRE))
    return EntityStorage.get().search(GdsAccountIndex.class, sq).getData().collect { it.source }.collect { new SabreGdsAccountWrapper(it) }.unique()
}

private static SearchQuery buildSearchQuery() {
    SearchQuery sq = new SearchQuery()
    return sq
}

private static Class getIndexClass() {
    return SalesPointIndex.class
}

private static RuleSet getRuleSet(SalesPoint entity) {
    return entity.ruleSet
}
