package ru.yandex.autotests.directapi.model;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import ch.lambdaj.function.convert.PropertyExtractor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.autotests.direct.utils.ReflectionUtils;
import ru.yandex.autotests.direct.utils.model.MongoUser;
import ru.yandex.autotests.direct.utils.money.Currency;
import ru.yandex.autotests.directapi.darkside.datacontainers.jsonrpc.fake.ClientFakeInfo;
import ru.yandex.autotests.directapi.exceptions.DirectAPIException;
import ru.yandex.autotests.directapi.model.balance.ContractInfo;
import ru.yandex.autotests.directapi.model.clients.ClientInfoMap;
import ru.yandex.autotests.directapi.objecthelpers.uiclient.data.types.Role;
import ru.yandex.autotests.directapi.steps.UserSteps;

import static ch.lambdaj.Lambda.convert;
import static org.apache.commons.lang3.StringUtils.capitalize;


/**
 * User: mariabye
 * Date: 20.06.13
 * Time: 11:12
 */
public class User {

    private final static Logger log = LoggerFactory.getLogger(User.class);
    private final MongoUser mongoUser;
    private String[] contractIDs;

    private String[] externalContractIDs;

    private ContractInfo[] contractInfos;

    @Deprecated
    private transient Role role;

    private transient UserSteps userSteps;
    private transient Object clientInfo;
    private transient ClientFakeInfo clientFakeInfo;
    private transient PaymentData paymentData;
    private transient Integer actualShard = 0;

    private final static Map<String, User> cache = Collections.synchronizedMap(new HashMap<>());

    public User() {
        mongoUser = new MongoUser();
    }

    public User(MongoUser mongoUser) {
        this.mongoUser = new MongoUser();
        setCertName(mongoUser.getCertName());
        setExpectedShard(mongoUser.getExpectedShard());
        setFinanceToken(mongoUser.getFinanceToken());
        setLogin(mongoUser.getLogin());
        setPassword(mongoUser.getPassword());
        setSandboxToken(mongoUser.getSandboxToken());
        setPaymentToken(mongoUser.getPaymentToken());
        setToken(mongoUser.getToken());
    }

    public static User get(String login) {
        if (cache.containsKey(login)) {
            log.info("Returning user from cache: {}", login);
            return cache.get(login);
        }

        log.info("Retrieving user from mongo: {}", login);
        User user = new User(MongoUser.get(login));
        cache.put(login, user);
        return user;
    }

    private UserSteps getUserSteps() {
        userSteps = UserSteps.getInstance();
        return userSteps;
    }

    private Object getClientInfo() {
        if (clientInfo == null) {
            clientInfo = getUserSteps().clientSteps().getClientInfo(getLogin());
        }
        return clientInfo;
    }

    private ClientFakeInfo getClientFakeInfo() {
        if (clientFakeInfo == null) {
            clientFakeInfo = getUserSteps().clientFakeSteps().getClientData(getLogin());
        }
        return clientFakeInfo;
    }

    private int getUserShard() {
        if (actualShard == 0) {
            actualShard = getUserSteps().clientFakeSteps().getUserShard(getLogin());
        }
        return actualShard;
    }

    private PaymentData getPaymentData() {
        if (paymentData == null) {
            paymentData = getUserSteps().balanceSteps().getPaymentData(getLogin());
        }
        return paymentData;
    }

    public Currency getCurrency() {
        String[] currencies =
                (String[]) ReflectionUtils.invokeGetter(getClientInfo(), capitalize(ClientInfoMap.CLIENT_CURRENCIES));
        if (currencies == null) {
            return Currency.YND_FIXED;
        }
        return Currency.getFor(currencies[0]);
    }

    public Role getRole() {
        if (this.role == null) {
            String roleName =
                    (String) ReflectionUtils.invokeGetter(this.getClientInfo(), StringUtils.capitalize("role"));
            this.role = Role.fromValue(roleName.toLowerCase());
        }

        return this.role;
    }

    public String getPassportUID() {
        return getClientFakeInfo().getPassportID();
    }

    public String getClientID() {
        return getClientFakeInfo().getClientID();
    }

    /**
     * @return personID or null, if the user does not have a personID
     */
    public Integer getPersonID() {
        try {
            return getPaymentData().getPersonID();
        } catch (Exception e) {
            throw new DirectAPIException("Ошибка получения ID плательщика в Балансе", e);
        }
    }

    /**
     * @return paySysID or null, if the user does not have a paySysID
     */
    public Integer getPaySysID() {
        return getPaymentData().getPaySysID();
    }

    private ContractInfo getContractInfo() {
        contractInfos = getContractInfos();
        if (contractInfos != null && contractInfos.length > 0) {
            return contractInfos[0];
        } else {
            return null;
        }
    }

    public ContractInfo[] getContractInfos() {
        if (getPersonID() == null || getPersonID() == 0) {
            // если возникает эта ошибка, надо починить (создать плательщика), записать юзернейм
            // в BalancePersonsSmokeTest и попросить Баланс создавать такого плательщика сразу после переналивки базы
            // (см. комментарий в BalancePersonsSmokeTest)
            throw new DirectAPIException("Ошибка получения информации по договорам в Балансе: нет плательщика");
        }

        try {
            if (contractInfos == null) {
                contractInfos = getUserSteps().balanceSteps().getContractInfo(getClientID(), getPersonID());
            }
            return contractInfos;
        } catch (Exception e) {
            throw new DirectAPIException("Ошибка получения информации по договорам в Балансе", e);
        }
    }

    public String[] getContractIDs() {
        if (contractIDs == null) {
            contractInfos = getContractInfos();
        }
        List<String> iDs = convert(contractInfos, new PropertyExtractor<ContractInfo, String>("id"));
        return iDs.toArray(new String[0]);
    }

    public String getContractID() {
        if (contractIDs == null) {
            contractIDs = getContractIDs();
        }
        if (contractIDs != null && contractIDs.length > 0) {
            return contractIDs[0];
        }
        return null;
    }

    public String[] getExternalContractIDs() {
        if (externalContractIDs == null) {
            contractInfos = getContractInfos();
        }
        List<String> iDs = convert(contractInfos, new PropertyExtractor<ContractInfo, String>("externalID"));
        return iDs.toArray(new String[0]);
    }

    public String getExternalContractID() {
        if (externalContractIDs == null) {
            externalContractIDs = getExternalContractIDs();
            if (externalContractIDs != null && externalContractIDs.length > 0) {
                return externalContractIDs[0];
            }
        }
        return null;
    }

    public void resetBalanceContractInfos() {
        contractInfos = null;
        contractIDs = null;
        externalContractIDs = null;
    }

    public String getCertName() {
        return mongoUser.getCertName();
    }

    public String getLogin() {
        return mongoUser.getLogin();
    }

    public void setLogin(String login) {
        mongoUser.setLogin(login);
    }

    public String getPassword() {
        return mongoUser.getPassword();
    }

    public void setPassword(String password) {
        mongoUser.setPassword(password);
    }

    public String getToken() {
        return mongoUser.getToken();
    }

    public void setToken(String token) {
        mongoUser.setToken(token);
    }

    public String getFinanceToken() {
        return mongoUser.getFinanceToken();
    }

    public void setFinanceToken(String financeToken) {
        mongoUser.setFinanceToken(financeToken);
    }

    public String getSandboxToken() {
        return mongoUser.getSandboxToken();
    }

    public void setSandboxToken(String sandboxToken) {
        mongoUser.setSandboxToken(sandboxToken);
    }

    public String getPaymentToken() {
        return mongoUser.getPaymentToken();
    }

    public void setPaymentToken(String paymentToken) {
        mongoUser.setPaymentToken(paymentToken);
    }

    public void setCertName(String certName) {
        mongoUser.setCertName(certName);
    }

    public int getExpectedShard() {
        return mongoUser.getExpectedShard();
    }

    public void setExpectedShard(int expectedShard) {
        mongoUser.setExpectedShard(expectedShard);
    }

    public String toString() {
        return mongoUser.toString();
    }

    public void saveInMongo() {
        cache.remove(getLogin());
        MongoUser.saveUser(mongoUser);
    }

    public void removeFromMongo() {
        cache.remove(getLogin());
        MongoUser.removeUser(getLogin());
    }

    public static List<User> getAll() {
        List<User> users = MongoUser.getAll().stream().map(User::new).collect(Collectors.toList());
        for (User u : users) {
            cache.put(u.getLogin(), u);
        }
        return users;
    }
}
