package ru.yandex.webmaster3.storage.achievements.dao;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.base.Strings;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.W3Collectors;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.achievements.model.AchievementInfo;
import ru.yandex.webmaster3.storage.achievements.model.AchievementProblem;
import ru.yandex.webmaster3.storage.achievements.model.AchievementService;
import ru.yandex.webmaster3.storage.achievements.model.AchievementTld;
import ru.yandex.webmaster3.storage.achievements.model.AchievementType;
import ru.yandex.webmaster3.storage.achievements.model.official.AchievementOfficialType;
import ru.yandex.webmaster3.storage.achievements.model.official.OfficialInfo;
import ru.yandex.webmaster3.storage.clickhouse.ClickhouseTableInfo;
import ru.yandex.webmaster3.storage.clickhouse.TableProvider;
import ru.yandex.webmaster3.storage.clickhouse.TableType;
import ru.yandex.webmaster3.storage.util.JsonDBMapping;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHRow;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.QueryBuilder;

/**
 * Created by Oleg Bazdyrev on 17/01/2019.
 */
@Slf4j
public class AchievementsCHDao extends AbstractClickhouseDao {

    private static final List<AchievementInfo> DEFAULT_ACHIEVEMENTS = Arrays.asList(
            new AchievementInfo.UserChoiceAchievementInfo(0),
            new AchievementInfo.PopularAchievementInfo(0),
            new AchievementInfo.TurboAchievementInfo(false)
    );

    private static final String[] COLUMNS_RU = {F.OWNER, F.USER_SELECTION, F.POPULAR, F.OFFICIAL_AUTO, F.OFFICIAL_MFO,
            F.OFFICIAL_SSD, F.OFFICIAL_GOV, F.OFFICIAL_YANDEX, F.OFFICIAL_CBR, F.OFFICIAL_CBRF_BKI,
            F.OFFICIAL_CBR_LICENSE_OFF,
            F.SERVICE_CENTER, F.BRANDS, F.UNSAFE_HOST, F.OFFICIAL_AIRLINE, F.OFFICIAL_EMBASSY, F.OFFICIAL_THEATRE,
            F.OFFICIAL_VISA_CENTER, F.HTTPS, F.TURBO_WM, F.OFFICIAL_CBRF_NPF, F.VIDEOHOST_RATING,
            F.OFFICIAL_CBRF_STOCK, F.OFFICIAL_CBRF_KOPURCB, F.OFFICIAL_CBRF_PROF
    };

    private static final String[] COLUMNS_ALL = {F.OWNER, F.USER_SELECTION, F.POPULAR, F.OFFICIAL_AUTO, F.OFFICIAL_MFO,
            F.OFFICIAL_SSD, F.OFFICIAL_GOV, F.OFFICIAL_YANDEX, F.OFFICIAL_CBR, F.OFFICIAL_CBRF_BKI,
            F.OFFICIAL_CBR_LICENSE_OFF,
            F.SERVICE_CENTER, F.BRANDS, F.UNSAFE_HOST, F.OFFICIAL_AIRLINE, F.OFFICIAL_EMBASSY, F.OFFICIAL_THEATRE,
            F.OFFICIAL_VISA_CENTER, F.HTTPS, F.SPEED_GRADE_MOBILE, F.TURBO_WM, F.DOCS_ON_SEARCH,
            F.OFFICIAL_CBRF_NPF, F.VIDEOHOST_RATING, F.OFFICIAL_CBRF_STOCK, F.OFFICIAL_CBRF_KOPURCB, F.OFFICIAL_CBRF_PROF
    };

    private static final String[] COLUMNS_NOT_RU = {F.OWNER, F.USER_SELECTION, F.POPULAR, F.OFFICIAL_GOV,
            F.OFFICIAL_AUTO, F.SERVICE_CENTER, F.BRANDS, F.UNSAFE_HOST, F.OFFICIAL_AIRLINE, F.OFFICIAL_EMBASSY,
            F.OFFICIAL_VISA_CENTER, F.OFFICIAL_YANDEX, F.TAS_IX, F.HTTPS, F.TURBO_WM, F.VIDEOHOST_RATING
    };

    @Autowired
    private AchievementService achievementService;
    @Autowired
    private AbtService abtService;

    private final int NO_VIDEOHOST_ACHIEVEMENT = -1;

    @Setter
    private TableProvider tableStorage;

    public AchievementsData getAll(AchievementTld tld, String owner) {
        return getBatchAll(tld, List.of(owner)).get(owner);
    }

    public Map<String, AchievementsData> getBatchAll(AchievementTld tld, Collection<String> owners) {
        return getAchievementsAndProblems(COLUMNS_ALL, tld, owners);
    }

    public AchievementsData getAchievementsAndProblems(AchievementTld tld, String owner) {
        return getBatchAchievementsAndProblems(tld, List.of(owner)).get(owner);
    }

    public Map<String, AchievementsData> getBatchAchievementsAndProblems(AchievementTld tld, Collection<String> owners) {
        return getAchievementsAndProblems(tld == AchievementTld.RU ? COLUMNS_RU : COLUMNS_NOT_RU, tld, owners);
    }

    private Map<String, AchievementsData> getAchievementsAndProblems(
            String[] columns, AchievementTld tld, Collection<String> owners) {
        boolean ru = tld == AchievementTld.RU;
        ClickhouseTableInfo table = tableStorage.getTable(ru
                ? TableType.ACHIEVEMENTS
                : TableType.ACHIEVEMENTS_KUUB);
        String query;
        if (ru) {
            query = QueryBuilder.select(columns).from(table.getLocalTableName())
                    .where(QueryBuilder.in(F.OWNER, owners)).toString();
        } else {
            query = QueryBuilder.select(columns).from(table.getLocalTableName())
                    .where(QueryBuilder.in(F.OWNER, owners)).and(QueryBuilder.eq(F.TLD, tld.name().toLowerCase()))
                    .toString();
        }

        Mapper mapper = new Mapper(tld, columns);


        Map<String, AchievementsData> achievementsByOwner = getClickhouseServer()
                .collectAll(query, Collectors.mapping(mapper, W3Collectors.toHashMap()));

        owners.forEach(owner -> {
            // если такие записи только на чтение то стоит создать один объект
            achievementsByOwner.putIfAbsent(owner, new AchievementsData(new ArrayList<>(DEFAULT_ACHIEVEMENTS),
                    new ArrayList<>()));
        });

        // если сервис Яндекс - выкинем остальные ачивки
        for (AchievementsData value : achievementsByOwner.values()) {
            AchievementInfo.OfficialAchievementInfo offInfo = getOfficialAchievementInfo(value);
            if (offInfo != null && offInfo.getValue().contains(AchievementOfficialType.YANDEX)) {
                value.getInfos().clear();
                value.getInfos().add(offInfo);
            }
        }
        return achievementsByOwner;
    }

    private static AchievementInfo.OfficialAchievementInfo getOfficialAchievementInfo(AchievementsData achievementsData) {
        return (AchievementInfo.OfficialAchievementInfo) achievementsData.getInfos().stream().filter(info -> info.getType() == AchievementType.OFFICIAL)
                .findAny().orElse(null);
    }

    public interface F {
        String OWNER = "owner";
        String TLD = "tld";
        String USER_SELECTION = "user_selection";
        String POPULAR = "popular";
        String OFFICIAL_AUTO = "official_auto";
        String OFFICIAL_MFO = "official_mfo";
        String OFFICIAL_SSD = "official_ssd";
        String OFFICIAL_GOV = "official_gov";
        String OFFICIAL_YANDEX = "official_yandex";
        String OFFICIAL_CBR = "official_cbr";
        String OFFICIAL_CBRF_BKI = "official_cbrf_bki";
        String OFFICIAL_CBR_LICENSE_OFF = "official_cbr_license_off";
        String SERVICE_CENTER = "service_center";
        String BRANDS = "brands";
        String UNSAFE_HOST = "unsafe_host";
        String OFFICIAL_AIRLINE = "official_airline";
        String OFFICIAL_EMBASSY = "official_embassy";
        String OFFICIAL_VISA_CENTER = "official_visa_center";
        String HTTPS = "https";
        String TAS_IX = "tas_ix";
        String OFFICIAL_CBRF_PROF = "official_cbrf_prof";
        String OFFICIAL_CBRF_STOCK = "official_cbrf_stock";
        String OFFICIAL_CBRF_KOPURCB = "official_cbrf_kopurcb";
        String OFFICIAL_CBRF_NPF = "official_cbrf_npf";
        String OFFICIAL_THEATRE = "official_theatre";
        String TURBO_WM = "turbo_wm";
        String SPEED_GRADE_MOBILE = "speed_grade_mobile";
        String DOCS_ON_SEARCH = "docs_on_search";
        String IKS = "iks";
        String VIDEOHOST_RATING = "videohost_rating";
    }

    private final class Mapper implements Function<CHRow, Pair<String, AchievementsData>> {

        private final AchievementTld tld;
        private final Set<String> columns;

        public Mapper(AchievementTld tld, String[] columns) {
            this.tld = tld;
            this.columns = new HashSet<>(Arrays.asList(columns));
        }

        private int getInt(CHRow chRow, String fieldName) {
            if (columns.contains(fieldName)) {
                return chRow.getInt(fieldName);
            }
            return 0;
        }

        private long getLong(CHRow chRow, String fieldName) {
            if (columns.contains(fieldName)) {
                return chRow.getLong(fieldName);
            }
            return 0L;
        }

        private String getString(CHRow chRow, String fieldName) {
            if (columns.contains(fieldName)) {
                return chRow.getString(fieldName);
            }
            return null;
        }

        private boolean getBoolean(CHRow chRow, String fieldName) {
            if (columns.contains(fieldName)) {
                return chRow.getInt(fieldName) == 1;
            }
            return false;
        }

        @Override
        public Pair<String, AchievementsData> apply(CHRow chRow) {
            try {
                return Pair.of(getString(chRow, F.OWNER), applyInternal(chRow));
            } catch (IOException e) {
                throw new WebmasterException("Wrong json",
                        new WebmasterErrorResponse.UnableToReadJsonRequestResponse(getClass(), e), e);
            }
        }

        public AchievementsData applyInternal(CHRow chRow) throws IOException {
            List<AchievementInfo> achievements = new ArrayList<>();
            if (achievementService.getAllAchievementTypes(tld).contains(AchievementType.USER_CHOICE)) {
                achievements.add(new AchievementInfo.UserChoiceAchievementInfo(getInt(chRow, F.USER_SELECTION)));
            }
            if (achievementService.getAllAchievementTypes(tld).contains(AchievementType.POPULAR)) {
                achievements.add(new AchievementInfo.PopularAchievementInfo(getInt(chRow, F.POPULAR)));
            }
            List<OfficialInfo> officialInfos = new ArrayList<>();
            if (getBoolean(chRow, F.OFFICIAL_AUTO)) {
                officialInfos.add(new OfficialInfo.AutoRuDealerInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_MFO)) {
                officialInfos.add(new OfficialInfo.MicroFinanceInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_SSD)) {
                officialInfos.add(new OfficialInfo.InsuranceInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_GOV)) {
                officialInfos.add(new OfficialInfo.GovermentInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_YANDEX)) {
                officialInfos.add(new OfficialInfo.YandexServiceInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBR)) {
                officialInfos.add(new OfficialInfo.CreditOrganizationInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBRF_BKI)) {
                officialInfos.add(new OfficialInfo.CbrfBkiInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBRF_NPF)) {
                officialInfos.add(new OfficialInfo.CbrfNpfInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBRF_STOCK)) {
                officialInfos.add(new OfficialInfo.CbrfStockInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBRF_KOPURCB)) {
                officialInfos.add(new OfficialInfo.CbrfKopurcbInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_CBRF_PROF)) {
                officialInfos.add(new OfficialInfo.CbrfProfInfo());
            }
            String serviceCenter = getString(chRow, F.SERVICE_CENTER);
            if (!Strings.isNullOrEmpty(serviceCenter)) {
                List<String> brands = new ArrayList<>();
                JsonDBMapping.OM.readTree(serviceCenter).elements()
                        .forEachRemaining(node -> brands.add(node.get("brand").asText()));
                String brandsString = String.join(", ", brands);
                officialInfos.add(new OfficialInfo.ServiceCenterInfo(brandsString));
            }
            String brand = getString(chRow, F.BRANDS);
            if (!Strings.isNullOrEmpty(brand)) {
                List<String> brands = new ArrayList<>();
                JsonDBMapping.OM.readTree(brand).elements()
                        .forEachRemaining(node -> brands.add(node.asText()));
                String brandsString = String.join(", ", brands);
                officialInfos.add(new OfficialInfo.BrandInfo(brandsString));
            }
            if (getBoolean(chRow, F.OFFICIAL_AIRLINE)) {
                officialInfos.add(new OfficialInfo.AirlineInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_EMBASSY)) {
                officialInfos.add(new OfficialInfo.EmbassyInfo());
            }
            if (getBoolean(chRow, F.OFFICIAL_VISA_CENTER)) {
                officialInfos.add(new OfficialInfo.VisaCenterInfo());
            }
            if (achievementService.getAllAchievementTypes(tld).contains(AchievementType.OFFICIAL) && !officialInfos.isEmpty()) {
                achievements.add(new AchievementInfo.OfficialAchievementInfo(officialInfos));
            }
            if (achievementService.getAllAchievementTypes(tld).contains(AchievementType.TAS_IX) && getBoolean(chRow,
                    F.TAS_IX)) {
                achievements.add(new AchievementInfo.TasIxAchievementInfo(true));
            }
            int https = getInt(chRow, F.HTTPS);
            if (https == 1) {
                achievements.add(new AchievementInfo.HttpsAchievementInfo(true));
            } else if (https == 2) {
                achievements.add(new AchievementInfo.HttpsAchievementInfo(false));
            }
            if (columns.contains(F.SPEED_GRADE_MOBILE)) {
                achievements.add(new AchievementInfo.MobileSpeedAchievementInfo(getInt(chRow, F.SPEED_GRADE_MOBILE)));
            }
            if (columns.contains(F.TURBO_WM)) {
                achievements.add(new AchievementInfo.TurboAchievementInfo(
                        getInt(chRow, F.TURBO_WM) == 1
                ));
            }
            if (columns.contains(F.DOCS_ON_SEARCH)) {
                achievements.add(new AchievementInfo.DocsOnSearch(getLong(chRow, F.DOCS_ON_SEARCH)));
            }

            if (columns.contains(F.IKS)) {
                achievements.add(new AchievementInfo.Iks(getInt(chRow, F.IKS)));
            }

            if (columns.contains(F.VIDEOHOST_RATING)) {
                String host = getString(chRow, F.OWNER);
                if (host != null && abtService.isInExperiment(IdUtils.urlToHostId(host), "VIDEOHOST_ACHIEVEMENTS")) {
                    long val = getLong(chRow, F.VIDEOHOST_RATING);
                    if (val != NO_VIDEOHOST_ACHIEVEMENT) {
                        achievements.add(new AchievementInfo.VideohostRatingInfo(val));
                    }
                }
            }

            // problems
            List<AchievementProblem> problems = new ArrayList<>();
            if (getBoolean(chRow, F.OFFICIAL_CBR_LICENSE_OFF)) {
                problems.add(new AchievementProblem.LicenseRevokedProblem());
            }
            if (getBoolean(chRow, F.UNSAFE_HOST)) {
                problems.add(new AchievementProblem.UnsafeHostProblem());
            }
            return new AchievementsData(achievements, problems);
        }
    }
}
