package ru.yandex.autotests.direct.web.helpers;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import org.hamcrest.Matcher;

import ru.yandex.autotests.direct.db.models.jooq.ppc.tables.records.CampaignsRecord;
import ru.yandex.autotests.direct.db.models.jooq.ppc.tables.records.WalletCampaignsRecord;
import ru.yandex.autotests.direct.utils.config.DirectTestRunProperties;
import ru.yandex.autotests.direct.utils.money.Currency;
import ru.yandex.autotests.direct.utils.money.Money;
import ru.yandex.autotests.direct.web.TestEnvironment;
import ru.yandex.autotests.direct.web.objects.campaigns.CampaignInfoWeb;
import ru.yandex.autotests.direct.web.steps.BackendSteps;
import ru.yandex.autotests.direct.web.util.DirectWebError;
import ru.yandex.autotests.directapi.apiclient.errors.AxisError;
import ru.yandex.autotests.directapi.common.api45.Account;
import ru.yandex.autotests.directapi.common.api45.ClientInfo;
import ru.yandex.autotests.directapi.common.api45mng.APIPort_PortType;
import ru.yandex.autotests.directapi.exceptions.DirectAPIException;
import ru.yandex.autotests.directapi.model.User;
import ru.yandex.autotests.directapi.rules.ApiSteps;
import ru.yandex.autotests.directapi.steps.ConditionFactories;
import ru.yandex.autotests.directapi.steps.UserSteps;
import ru.yandex.qatools.allure.annotations.Step;
import ru.yandex.qatools.allure.webdriver.annotations.WithoutScreenshot;

import static ru.yandex.autotests.direct.web.TestEnvironment.newDbSteps;

/**
 * User: buhter
 * Date: 07.11.13
 * Time: 17:11
 */
public class FinanceApiHelper {

    public static Currency getClientCurrency(User client) {
        Currency currency = client.getCurrency();
        return currency == Currency.YND_FIXED ? null : currency;
    }

    public UserSteps getUserSteps(User user) {
        ApiSteps api = TestEnvironment.getDirectApi();
        api.as(user.getLogin());
        return api.userSteps;
    }

    private UserSteps getManagerSteps(User manager) {
        ApiSteps api = TestEnvironment.getDirectApi().wsdl(APIPort_PortType.class);
        api.as(manager.getLogin());
        return api.userSteps;
    }

    private UserSteps getUserSteps() {
        return TestEnvironment.getDirectApi().userSteps;
    }

    public BackendSteps getBackendSteps() {
        return new BackendSteps();
    }

    @Step("Через api создаем активную кампанию для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createActiveCampaignWithMoney(User client, Money sum) {
        Long campaignId = getBackendSteps().createClientActiveCampaign(client.getLogin());
        putMoneyToCampaign(client, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }

    @Step("Через api создаем активную кампанию для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createActiveMobileAppCampaignWithMoney(User client, Money sum) {
        Long campaignId = getBackendSteps().createClientActiveMobileAppCampaign(client.getLogin());
        putMoneyToCampaign(client, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }

    @Step("обновление записи об общем счете в базе данных")
    private void updateClientAccount(Long accountId, String client) {
        WalletCampaignsRecord walletCampaignsRecord = newDbSteps().useShardForLogin(client)
                .walletCampaignsSteps().getWalletCampaigns(accountId);
        Timestamp twoDaysAgo = Timestamp.from(Instant.now().minusSeconds(TimeUnit.DAYS.toSeconds(2)));
        walletCampaignsRecord.setOnoffDate(twoDaysAgo)
                .setLastChange(twoDaysAgo);
        newDbSteps().useShardForLogin(client)
                .walletCampaignsSteps()
                .updateWalletCampaigns(walletCampaignsRecord);
    }

    @Step("Через api создаем активную текстовую кампанию под {2} для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createActiveCampaignWithMoney(User client, Money sum, User agency) {
        User campaignCreator = agency == null ? client : agency;
        Long campaignId;

        if (agency == null) {
            campaignId = getBackendSteps().createClientActiveCampaign(client.getLogin());
        } else {
            campaignId = getBackendSteps().createManagerActiveCampaign(campaignCreator.getLogin(), client.getLogin());
        }
        putMoneyToCampaign(campaignCreator, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }

    @Step("Через api создаем активную РМП кампанию под {2} для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createActiveMobileAppCampaignWithMoney(User client, Money sum, User agency) {
        Long campaignId = getBackendSteps().createManagerActiveMobileAppCampaign(agency.getLogin(), client.getLogin());
        putMoneyToCampaign(client, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }


    @Step("Через api создаем активную кампанию под {2} для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createManagerActiveCampaignWithMoney(User client, Money sum, User manager) {
        Long campaignId = getBackendSteps().createManagerActiveCampaign(manager.getLogin(), client.getLogin());
        putMoneyToCampaign(manager, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }

    @Step("Через api создаем активную РМП-кампанию под {2} для {0} с остатком {1}")
    @WithoutScreenshot
    public Long createManagerActiveMobileAppCampaignWithMoney(User client, Money sum, User manager)
    {
        Long campaignId = getBackendSteps().createManagerActiveMobileAppCampaign(manager.getLogin(), client.getLogin());
        putMoneyToCampaign(manager, campaignId, sum, getClientCurrency(client));
        return campaignId;
    }

    @Step("Через api включаем общий счет под {0} для {1}")
    @WithoutScreenshot
    public Long enableAccount(User agency, User account) {
        Long accountId =
                Long.valueOf(getUserSteps(agency).financeSteps().enableAndGetSharedAccount(account.getLogin()));
        synchronizeWithBalance(accountId);
        return accountId;
    }

    @Step("Через api разрешаем юзеру {0} создавать кампании")
    @WithoutScreenshot
    public void enableToCreateSelfCampaigns(User user) {
        getUserSteps().clientFakeSteps().enableToCreateSelfCampaigns(user.getLogin());
    }

    @Step("Через api посылаем фейковую нотификацию для кампании {0} остаток {1}")
    @WithoutScreenshot
    public void sendFakeNotificationFromBalance(Long campaignId, Money sum, Currency currency) {
        getUserSteps().campaignFakeSteps().sendFakeNotificationFromBalance(campaignId, sum.floatValue(), currency);
    }

    @Step("Через api баланса проверяем, что сумма в балансе для requestID {1} {2}")
    @WithoutScreenshot
    public void requestAmountShouldBe(User user, String requestID, Matcher<Float> matcher) {
        UserSteps steps = getUserSteps();
        steps.balanceSteps().operator(user).requestAmountShouldBe(requestID, matcher);
    }

    @Step("Через api баланса создаем инвойс {1} в балансе для юзера {0}")
    @WithoutScreenshot
    public void confirmRequest(User user, String requestID) {
        UserSteps steps = getUserSteps();
        steps.balanceSteps().operator(user).turnOnInvoice(steps.balanceSteps().operator(user).createInvoice(requestID));
    }

    private Account getAccount(Long accountId) {
        return getUserSteps().financeSteps().getAccount(accountId.intValue());
    }

    @Step("Запрос суммы на аккаунте {0}")
    @WithoutScreenshot
    public float getAccountAmount(User client, User agency) {
        Account account = getUserSteps(agency).financeSteps().getAccount(client.getLogin());
        return account.getAmount();
    }

    @Step
    @WithoutScreenshot
    public Long getAccountId(User client, User agency) {
        int accountId = ((Account) getUserSteps(agency).financeSteps().getAccount(client.getLogin())).getAccountID();
        return Long.valueOf(accountId);
    }

    @Step
    @WithoutScreenshot
    public Long getAccountId(User client) {
        return Long.valueOf(getUserSteps().financeSteps().getAccountID(client.getLogin()));
    }

    @Step
    @WithoutScreenshot
    public float getAccountAmount(Long accountId) {
        return getAccount(accountId).getAmount();
    }

    @Step
    @WithoutScreenshot
    public ClientInfo getClientInfo(User user) {
        try {
            return getUserSteps().clientSteps().getClientInfo(user.getLogin());
        } catch (AxisError axisError) {
            throw new DirectAPIException("Ошибка получения через api информации о клиенте " + user.getLogin(),
                    axisError);
        }
    }

    @Step("Через api создаем нового клиена под менеджером {0}")
    @WithoutScreenshot
    public User createNewClient(User manager) {
        ru.yandex.autotests.directapi.common.api45mng.CreateNewSubclientResponse response =
                getManagerSteps(manager).clientSteps().createServicedClient("account-serviced-", manager.getLogin());
        User client = new User();
        client.setLogin(response.getLogin().toLowerCase());
        client.setPassword(response.getPassword());
        createPerson(client);
        return client;
    }

    @Step("Через api создаем под агентством {0} субклиента с валютой {2}")
    @WithoutScreenshot
    public User createSubClient(User agency, String loginBase, Currency currency) {
        ru.yandex.autotests.directapi.common.api45.CreateNewSubclientResponse createNewSubclientResponse
                = getUserSteps(agency).clientSteps().createNewAgencySubClient(loginBase, agency.getLogin(), currency);
        User user = new User();
        user.setLogin(createNewSubclientResponse.getLogin().toLowerCase());
        user.setPassword(createNewSubclientResponse.getPassword());
        createPerson(user);
        return user;
    }

    @Step("Через api создаем юзера в балансе {0} через api")
    @WithoutScreenshot
    public void createPerson(User user) {
        getUserSteps().balanceSteps().createPersonPh(Integer.parseInt(user.getClientID()));
    }

    @Step("Через api синхронизируем сумму кампании {0} с балансом")
    @WithoutScreenshot
    public void synchronizeWithBalance(Long campaignId) {
        getUserSteps().balanceSteps().synchronizeWithBalance(campaignId);
    }

    @Step("В БД выставляем сумму кампании {0} в {1}")
    @WithoutScreenshot
    public void setSumInDb(Long campaignId, Money sum) {
        int shard = newDbSteps().shardingSteps().getShardByCid(campaignId);
        CampaignsRecord campaignsRecord = newDbSteps().useShard(shard).campaignsSteps()
                .getCampaignById(campaignId);
        campaignsRecord.setSum(sum.bigDecimalValue());
        campaignsRecord.setSumLast(sum.bigDecimalValue());
        campaignsRecord.setSumUnits(sum.bigDecimalValue().longValue());
        newDbSteps().useShard(shard).campaignsSteps().updateCampaigns(campaignsRecord);
    }

    public void synchronizeWithBalance(List<CampaignInfoWeb> campaignInfoList) {
        campaignInfoList.stream().forEach(t -> synchronizeWithBalance(t.getCampaignId()));
    }

    public Callable<Boolean> campaignFundsChanged(long campaignId) {
        return campaignFundsChanged(campaignId, DirectTestRunProperties.getInstance().isDirectUseRealNotifyOrder());
    }

    public Callable<Boolean> campaignFundsChanged(long campaignId, boolean waitForRealNotification) {
        int shard = newDbSteps().shardingSteps().getShardByCid(campaignId);
        return () -> {
            // синхронизируемся с балансом перед получением суммы, что бы ускорить тесты
            if (!waitForRealNotification) {
                synchronizeWithBalance(campaignId);
            }
            return newDbSteps().useShard(shard).campaignsSteps()
                    .getCampaignById(campaignId)
                    .getSum()
                    .compareTo(BigDecimal.ZERO) > 0;
        };
    }


    @Step("Через API кладем {2} {3} от имени {0} на кампанию {1}")
    @WithoutScreenshot
    public void putMoneyToCampaign(User user, Long campaignId, Money amount, Currency currency) {
        float moneySum = amount.addVAT(currency).floatValue();
        if (moneySum != 0f) {
            int invoiceId = getUserSteps(user).createInvoice(campaignId.intValue(), moneySum, currency);
            getUserSteps().balanceSteps().turnOnInvoice(invoiceId);
            ConditionFactories.NOTIFY_PAYMENT.until(campaignFundsChanged(campaignId));
        }
    }

    @Step("Через API кладем {2} {3} из-под {0} на общий счет клиента {1}")
    @WithoutScreenshot
    public void putMoneyToAccount(User agency, User client, Money amount) {
        float moneySum = amount.addVAT(amount.getCurrency()).floatValue();
        if (moneySum != 0f) {
            getUserSteps(agency).payAccountWithBalance(client.getLogin(), moneySum, amount.getCurrency());
            Long accountId = getAccountId(client, agency);
            synchronizeWithBalance(accountId);
        }
    }

    @Step("Подключен ли общий счет у клиента {0}")
    public Boolean isWalletEnable(String login) {
        try {
            User user = User.get(login);
            getAccountId(user);
        } catch (DirectAPIException e) {
            if (e.getMessage().contains("Отсутствуют данные об общем счете у пользователя")) {
                return false;
            } else {
                throw new DirectWebError("Ошибка при получении данных через API");
            }
        }
        return true;
    }

    @Step("Подключен ли общий счет у клиента {0} агенства {1}")
    public Boolean isWalletEnable(String client, String agency) {
        User agencyUser = User.get(agency);
        Account account = getUserSteps(agencyUser).financeSteps().getAccount(client);
        return account == null ? false : true;
    }

    @Step("Через api баланса выдаем клиенту {0} доступный лимит {1}")
    public void setClientOverdraft(Long clientId, int autoOverdraftLimit) {
        getUserSteps().balanceNewSteps().setClientOverdraft(clientId, autoOverdraftLimit);
    }
}
