package ru.yandex.qe.dispenser.domain.dao.quota.request.collector;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.ParametersAreNonnullByDefault;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.postgresql.util.PGobject;

import ru.yandex.qe.dispenser.api.v1.DiResourcePreorderReasonType;
import ru.yandex.qe.dispenser.domain.Campaign;
import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.QuotaChangeRequest;
import ru.yandex.qe.dispenser.domain.Resource;
import ru.yandex.qe.dispenser.domain.Segment;
import ru.yandex.qe.dispenser.domain.dao.SqlUtils;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;

import static ru.yandex.qe.dispenser.domain.dao.SqlDaoBase.getBoolean;
import static ru.yandex.qe.dispenser.domain.dao.SqlDaoBase.getLong;
import static ru.yandex.qe.dispenser.domain.dao.goal.SqlGoalDao.toGoal;
import static ru.yandex.qe.dispenser.domain.dao.quota.request.SqlQuotaChangeRequestDao.CHART_LINKS_DELIMITER;
import static ru.yandex.qe.dispenser.domain.dao.quota.request.SqlQuotaChangeRequestDao.PROPERTIES_TYPE;

@ParametersAreNonnullByDefault
public abstract class BaseRequestCollector<T> implements RequestCollector<T> {
    public static final TypeReference<Map<Long, String>> REQUEST_GOAL_ANSWERS_TYPE = new TypeReference<>() {
    };
    protected final Map<Long, List<QuotaChangeRequest.Change>> changesByRequestId = new HashMap<>();
    protected final Map<Long, Set<Segment>> segmentByRequestChangeId = new HashMap<>();
    protected final List<QuotaChangeRequest.Builder> requestsBuilders = new ArrayList<>();
    protected final Hierarchy hierarchy = Hierarchy.get();
    protected final Table<Long, Boolean, QuotaChangeRequest.BigOrder> bigOrderCache = HashBasedTable.create();
    protected final Map<Long, QuotaChangeRequest.Campaign> campaignCache = new HashMap<>();

    @NotNull
    public static QuotaChangeRequest.Builder toQuotaRequestBuilder(@NotNull final ResultSet rs) throws SQLException {
        final Hierarchy hierarchy = Hierarchy.get();
        final Person author = hierarchy.getPersonReader().read(rs.getLong("author_id"));
        final Project project = hierarchy.getProjectReader().read(rs.getLong("project_id"));

        final Long sourceProjectId = getLong(rs, "source_project_id");
        final Project sourceProject = sourceProjectId == null ? null : hierarchy.getProjectReader().read(sourceProjectId);

        final String[] chartLinks = StringUtils.splitByWholeSeparator(rs.getString("chart_links"), CHART_LINKS_DELIMITER);
        final String chartLinksAbsenceExplanation = rs.getString("chart_links_absence_explanation");
        final String resourcePreorderReasonType = rs.getString("resource_preorder_reason_type");
        final Object requestGoalAnswers = rs.getObject("request_goal_answers");

        return new QuotaChangeRequest.Builder()
                .id(rs.getLong("id"))
                .project(project)
                .author(author)
                .created(SqlUtils.toTime(rs.getTimestamp("created")))
                .updated(SqlUtils.toTime(rs.getTimestamp("updated")))
                .description(rs.getString("description"))
                .comment(rs.getString("comment"))
                .calculations(rs.getString("calculations"))
                .trackerIssueKey(rs.getString("ticket_key"))
                .status(QuotaChangeRequest.Status.valueOf(rs.getString("status")))
                .sourceProject(sourceProject)
                .type(QuotaChangeRequest.Type.valueOf(rs.getString("type")))
                .chartLinks(chartLinks == null ? Collections.emptyList() : Arrays.asList(chartLinks))
                .chartLinksAbsenceExplanation(chartLinksAbsenceExplanation)
                .additionalProperties(SqlUtils.fromJsonb((PGobject) rs.getObject("additional_properties"), PROPERTIES_TYPE))
                .resourcePreorderReasonType(
                        resourcePreorderReasonType != null ? DiResourcePreorderReasonType.valueOf(resourcePreorderReasonType) : null)
                .goal(toGoal(rs))
                .readyForAllocation(rs.getBoolean("ready_for_allocation"))
                .requestGoalAnswers(requestGoalAnswers != null ? SqlUtils.fromJsonb((PGobject) requestGoalAnswers, REQUEST_GOAL_ANSWERS_TYPE) : null)
                .cost(rs.getDouble("cost"))
                .summary(rs.getString("summary"))
                .showAllocationNote(rs.getBoolean("show_allocation_note"))
                .requestOwningCost(rs.getLong("request_owning_cost"))
                .importantRequest(rs.getBoolean("important_request"))
                .unbalanced(rs.getBoolean("unbalanced"))
                .campaignType(Campaign.Type.valueOf(rs.getString("campaign_type")));
    }

    @Nullable
    public static QuotaChangeRequest.BigOrder toBigOrder(@NotNull final ResultSet rs,
                                                         @NotNull final Table<Long, Boolean, QuotaChangeRequest.BigOrder> bigOrdersCache) throws
            SQLException {
        final Long bigOrderId = getLong(rs, "order_id");
        if (bigOrderId == null) {
            return null;
        }
        final Boolean campaignBigOrderDeleted = getBoolean(rs, "campaign_big_order_deleted");
        final boolean inCampaign = campaignBigOrderDeleted != null && !campaignBigOrderDeleted;
        if (bigOrdersCache.contains(bigOrderId, inCampaign)) {
            return bigOrdersCache.get(bigOrderId, inCampaign);
        }
        final LocalDate bigOrderDate = LocalDate.parse(rs.getString("bot_big_order_date"), DateTimeFormatter.ISO_LOCAL_DATE);
        final QuotaChangeRequest.BigOrder bigOrder = new QuotaChangeRequest.BigOrder(bigOrderId, bigOrderDate, inCampaign);
        bigOrdersCache.put(bigOrderId, inCampaign, bigOrder);
        return bigOrder;
    }

    @Nullable
    private static QuotaChangeRequest.Campaign toCampaign(@NotNull final ResultSet rs,
                                                          @NotNull final Map<Long, QuotaChangeRequest.Campaign> campaignCache) throws
            SQLException {
        final Long campaignId = getLong(rs, "campaign_id");
        if (campaignId == null) {
            return null;
        }
        if (campaignCache.containsKey(campaignId)) {
            return campaignCache.get(campaignId);
        }
        final String key = rs.getString("campaign_key");
        final String name = rs.getString("campaign_name");
        final Campaign.Status status = Campaign.Status.valueOf(rs.getString("campaign_status"));
        final boolean requestModificationDisabledForNonManagers = rs.getBoolean("campaign_request_modification_disabled_for_non_managers");
        final boolean deleted = rs.getBoolean("campaign_deleted");
        final boolean allowedRequestModificationWhenClosed = rs.getBoolean("campaign_allowed_request_modification_when_closed");
        final boolean allowedModificationOnMissingAdditionalFields = rs.getBoolean("campaign_allowed_modification_on_missing_additional_fields");
        final Campaign.Type type = Campaign.Type.valueOf(rs.getString("campaign_campaign_type"));
        final QuotaChangeRequest.Campaign campaign = new QuotaChangeRequest.Campaign(campaignId, key, name, status,
                requestModificationDisabledForNonManagers, deleted, allowedRequestModificationWhenClosed,
                allowedModificationOnMissingAdditionalFields,
                rs.getBoolean("campaign_forced_allowing_request_status_change"),
                rs.getBoolean("campaign_single_provider_request_mode"),
                rs.getBoolean("campaign_allowed_request_creation_for_provider_admin"),
                rs.getBoolean("campaign_request_creation_disabled"),
                rs.getBoolean("allowed_request_creation_for_capacity_planner"),
                type,
                rs.getBoolean("campaign_request_modification_disabled"));
        campaignCache.put(campaignId, campaign);
        return campaign;
    }


    @Override
    public void processRow(final ResultSet rs) throws SQLException {
        final long requestId = rs.getLong("id");
        if (!changesByRequestId.containsKey(requestId)) {
            processFirstRequestRow(rs, requestId);
        }

        final long changeId = rs.getLong("quota_request_change_id");
        if (!segmentByRequestChangeId.containsKey(changeId)) {
            final long resourceId = rs.getLong("resource_id");

            final Resource resource = hierarchy.getResourceReader().read(resourceId);

            final Set<Segment> segments = new HashSet<>();

            final QuotaChangeRequest.Change change = QuotaChangeRequest.Change.builder()
                    .id(changeId)
                    .order(toBigOrder(rs, bigOrderCache))
                    .resource(resource)
                    .segments(segments)
                    .amount(rs.getLong("amount"))
                    .amountReady(rs.getLong("amount_ready"))
                    .amountAllocated(rs.getLong("amount_allocated"))
                    .amountAllocating(rs.getLong("amount_allocating"))
                    .owningCost(new BigDecimal(rs.getString("owning_cost")))
                    .build();

            changesByRequestId.get(requestId).add(change);
            segmentByRequestChangeId.put(changeId, segments);
        }

        final Long segmentId = getLong(rs, "segment_id");

        if (segmentId != null) {
            final Segment segment = hierarchy.getSegmentReader().read(segmentId);
            segmentByRequestChangeId.get(changeId).add(segment);
        }
    }

    protected void processFirstRequestRow(final ResultSet rs, final long requestId) throws SQLException {
        final ArrayList<QuotaChangeRequest.Change> changes = new ArrayList<>();
        QuotaChangeRequest.Campaign campaign = toCampaign(rs, campaignCache);
        final QuotaChangeRequest.Builder requestBuilder = toQuotaRequestBuilder(rs)
                .changes(changes)
                .campaign(campaign)
                .campaignType(campaign != null ? campaign.getType() : null);

        changesByRequestId.put(requestId, changes);
        requestsBuilders.add(requestBuilder);
    }
}
