#include "shopservice.h"
#include "numberseriesservice.h"
#include "isellableservice.h"
#include "shop-odb.hxx"

#include "settings/shopsettings.h"

#include <eetcpp.h>
#include <QEventLoop>
#include <seasonservice.h>
#include <math.h>

#ifdef _WIN32
inline double round(double value) { return value < 0 ? -std::floor(0.5 - value) : std::floor(0.5 + value); }
#endif

ShopService::ShopService()
{
}

VoucherPtr ShopService::createVoucher()
{
    QSharedPointer<Voucher> voucher(new Voucher);

    loadSettings();
    voucher->setVatRateHigh(m_gs->vatHigh());
    voucher->setVatRateFirstLower(m_gs->vatFirstLower());
    voucher->setVatRateSecondLower(m_gs->vatSecondLower());

    voucher->setStatus(Voucher::NEW);
    return voucher;
}

void ShopService::addShopItem(VoucherPtr voucher, QSharedPointer<IShopItem> item, int count)
{
    QSharedPointer<VoucherItem> vItem(new VoucherItem);
    vItem->setName(item->name());
    vItem->setUnitPrice(item->unitPrice());
    vItem->setCount(count);
    vItem->setRefId(item->id());
    vItem->setItemPlugin(item->pluginId());
    vItem->setVatType(item->vatType());
    vItem->setInsertDate(QDateTime::currentDateTime());

    voucher->addItem(vItem);

    updateRelatedItem(vItem.data(), count);
}

void ShopService::calculate(VoucherPtr voucher)
{
    QDecDouble total;

    voucher->setPriceNoVat(0);
    voucher->setPriceVatHigh(0);
    voucher->setPriceVatFirstLower(0);
    voucher->setPriceVatSecondLower(0);

    voucher->setTotalPriceVatHigh(0);
    voucher->setTotalPriceVatFirstLower(0);
    voucher->setTotalPriceVatSecondLower(0);

    loadSettings();

    foreach (QSharedPointer<VoucherItem> item, voucher->items()) {

        if (item->refId() == ROUNDING_ITEM)
        {
            voucher->removeItem(item);
            continue;
        }

        calculateItem(item);

        QDecDouble itemPrice;

        if (item->vatType() == Enums::NONE)
        {
            voucher->setPriceNoVat(voucher->priceNoVat() + item->priceWitouthVat());
        }

        if (m_gs->pricesWithVAT())
        {
            itemPrice = item->price();
            switch (item->vatType()) {
            case Enums::HIGH:
                voucher->setTotalPriceVatHigh(voucher->totalPriceVatHigh() + itemPrice);
                break;
            case Enums::FIRST_LOWER:
                voucher->setTotalPriceVatFirstLower(voucher->totalPriceVatFirstLower() + itemPrice);
                break;
            case Enums::SECOND_LOWER:
                voucher->setTotalPriceVatSecondLower(voucher->totalPriceVatSecondLower() + itemPrice);
                break;
            default:
                break;
            }
        }
        else
        {
            itemPrice = item->priceWitouthVat();
            switch (item->vatType()) {
            case Enums::HIGH:
                voucher->setPriceVatHigh(voucher->priceVatHigh() + itemPrice);
                break;
            case Enums::FIRST_LOWER:
                voucher->setPriceVatFirstLower(voucher->priceVatFirstLower() + itemPrice);
                break;
            case Enums::SECOND_LOWER:
                voucher->setPriceVatSecondLower(voucher->priceVatSecondLower() + itemPrice);
                break;
            default:
                break;
            }
        }

        total += item->price();
    }

    if (m_gs->pricesWithVAT())
    {
        voucher->setPriceVatHigh(excludeVat(voucher->totalPriceVatHigh(), Enums::HIGH));
        voucher->setPriceVatFirstLower(excludeVat(voucher->totalPriceVatFirstLower(), Enums::FIRST_LOWER));
        voucher->setPriceVatSecondLower(excludeVat(voucher->totalPriceVatSecondLower(), Enums::SECOND_LOWER));
    }
    else
    {
        voucher->setTotalPriceVatHigh(includeVat(voucher->priceVatHigh(), Enums::HIGH));
        voucher->setTotalPriceVatFirstLower(includeVat(voucher->priceVatFirstLower(), Enums::FIRST_LOWER));
        voucher->setTotalPriceVatSecondLower(includeVat(voucher->priceVatSecondLower(), Enums::SECOND_LOWER));
    }

    voucher->setTotalPrice(total);
    roundVoucher(voucher);
}

void ShopService::calculateItem(VoucherItemPtr item)
{
    loadSettings();
    if (m_gs->vatPayer())
    {
        item->setVatRate(vatRate(item->vatType()));

        if (m_gs->pricesWithVAT())
        {
            item->setPrice(item->unitPrice() * item->count());
            item->setPriceWitouthVat(excludeVat(item->price(), item->vatType()));
        }
        else
        {
            item->setPriceWitouthVat(item->unitPrice() * item->count());
            item->setPrice(includeVat(item->priceWitouthVat(), item->vatType()));
        }
    }
    else
    {
        item->setPrice(item->unitPrice() * item->count());
        item->setPriceWitouthVat(item->price());
    }
}

void ShopService::loadItems(VoucherPtr voucher)
{
    Service<VoucherItem> srv;
    voucher->setItems(srv.all(QString("voucher = %1").arg(voucher->id())));
}

void ShopService::pay(VoucherPtr voucher)
{
    Transaction tx;
    NumberSeriesService srvNs;

    voucher->setNumSer(srvNs.nextStrForPlugin("SHOP"));
    voucher->setStatus(Voucher::PAID);
    voucher->setEetStatus(Voucher::EET_FOR_SEND);
    voucher->setPayDateTime(QDateTime::currentDateTime());

    this->update(voucher);

    tx.commit();
}

void ShopService::updateRelatedItem(VoucherItem* item, int countAdded)
{
    IPlugin *plugin = Context::instance().plugin(item->itemPlugin());
    IService *srv = (plugin != NULL ? plugin->service<IService>() : NULL);
    ISellableService *selSrv = dynamic_cast<ISellableService*>(srv);

    if (selSrv != NULL)
    {
        selSrv->addedToVoucher(item->refId(), countAdded);
    }
}

bool ShopService::processEet(VoucherPtr voucher, QString &message)
{
#ifdef EET
    if (voucher->eetStatus() == Voucher::EET_NOT_ENTERING)
    {
        return true;
    }

    SettingsService srvSettings("SHOP");
    ShopSettingsPtr settings = srvSettings.loadSettings<ShopSettings>();

    loadSettings();
    EetRequest request;
    request.setCelkTrzba(voucher->totalPrice().toDouble());
    request.setDatTrzby(voucher->payDateTime());
    request.setIdPokl(settings->eetRegisterId());
    request.setIdProvoz(settings->eetShopId());
    request.setPrvniZaslani(voucher->eetStatus() == Voucher::EET_FOR_SEND);
    request.setDicPopl(m_gs->dic());
    request.setPoradCis(voucher->numSer());
    request.setDatOdesl(QDateTime::currentDateTime());
    request.setRezim((EetRequest::EetRezim)settings->eetMode());

    if (m_gs->vatPayer())
    {
        request.setDan1(voucher->vatAmountHigh().toDouble());
        request.setDan2(voucher->vatAmountFirstLower().toDouble());
        request.setDan3(voucher->VatAmountSecondLower().toDouble());

        request.setZaklDan1(voucher->priceVatHigh().toDouble());
        request.setZaklDan2(voucher->priceVatFirstLower().toDouble());
        request.setZaklDan3(voucher->priceVatSecondLower().toDouble());
        request.setZaklNepodlDph(voucher->priceNoVat().toDouble());
    }

    EetSender *sender = new EetSender(this);
    sender->setupSigner(settings->eetCertificate(), settings->eetKeyPassword());
    sender->setPlayground(settings->eetPlayground());

    QEventLoop loop;
    bool replyFinished = false;

    connect(sender, &EetSender::sendFinished, [this, voucher, sender, &loop, &replyFinished](){
        Transaction tx;

        voucher->setEetBkp(sender->resut()->bkp());
        voucher->setEetPkp(sender->resut()->pkp());
        voucher->setEetFik(sender->resut()->fik());

        if (sender->resut()->status() == EetResult::RESPONSE_OK)
        {
            voucher->setEetSendDateTime(QDateTime::currentDateTime());
            voucher->setEetStatus(Voucher::EET_SENT);
        }
        else
        {
            voucher->setEetStatus(Voucher::EET_ERROR);
        }

        this->update(voucher);
        tx.commit();
        sender->deleteLater();

        loop.quit();
        replyFinished = true;
    });

    sender->sendRequest(&request);

    if (!replyFinished)
    {
        loop.exec();
    }

    auto addMessage = [&message](EetMessage *msg, const QString &label){
        if (message.isEmpty())
        {
            message = label + "\n";
        }

        message += QString::number(msg->code()) + ": " + msg->message();
    };

    foreach (EetMessage *msg, sender->resut()->errors()) {
        addMessage(msg, "Errors:");
    }

    foreach (EetMessage *msg, sender->resut()->warnings()) {
        addMessage(msg, "Warnings:");
    }

    return voucher->eetStatus() == Voucher::EET_SENT;
#else
    return true;
#endif
}

void ShopService::setEetOnline(bool online)
{
#ifdef EET
    EetSender::m_online = online;
#endif
}

bool ShopService::isEetOnline()
{
#ifdef EET
    return EetSender::m_online;
#else
    return false;
#endif
}

bool ShopService::isEetEnabled()
{
    SettingsService srvSettings("SHOP");
    ShopSettingsPtr settings = srvSettings.loadSettings<ShopSettings>();
    return settings->eetActive();
}

void ShopService::roundVoucher(VoucherPtr voucher)
{
    SettingsService setSrv("SHOP");
    ShopSettingsPtr settings = setSrv.loadSettings<ShopSettings>();
    QDecDouble totalPrice(voucher->totalPrice());

    switch (settings->rounding()) {
    case Enums::R_UP:
        totalPrice = QDecDouble(ceil(voucher->totalPrice().toDouble() * pow(10, settings->decimalPlaces())) / pow(10, settings->decimalPlaces()));
        break;
    case Enums::R_DOWN:
        totalPrice = QDecDouble(floor(voucher->totalPrice().toDouble() * pow(10, settings->decimalPlaces())) / pow(10, settings->decimalPlaces()));
        break;
    case Enums::R_MATH:
        totalPrice = QDecDouble(round(voucher->totalPrice().toDouble() * pow(10, settings->decimalPlaces())) / pow(10, settings->decimalPlaces()));
        break;
    case Enums::R_NONE:
        break;
    }

    VoucherItemPtr roundingItem(new VoucherItem);
    roundingItem->setCount(1);
    roundingItem->setUnitPrice(totalPrice - voucher->totalPrice());
    roundingItem->setVatType(Enums::NONE);
    roundingItem->setName(settings->roundingItem());
    roundingItem->setRefId(ROUNDING_ITEM);
    roundingItem->setVoucher(voucher);

    calculateItem(roundingItem);

    if (roundingItem->price() != QDecDouble(0))
    {
        voucher->addItem(roundingItem);
        voucher->setPriceNoVat(voucher->priceNoVat() + roundingItem->price());
        voucher->setTotalPrice(totalPrice);
    }
}

VoucherItemPtr ShopService::roundingItem(VoucherPtr voucher)
{
    foreach (VoucherItemPtr item, voucher->items()) {
        if (item->refId() == ROUNDING_ITEM)
        {
            return item;
        }
    }

    return VoucherItemPtr();
}

void ShopService::moveItems(QList<VoucherItemPtr> items, VoucherPtr source, VoucherPtr target)
{
    Transaction tx;

    if (target->status() == Voucher::NEW && target->id() == 0)
    {
        this->saveVoucher(target);
    }

    odb::database *db = Context::instance().db();

    foreach (VoucherItemPtr item, items) {
        QString sql = QString("update VoucherItem set voucher = %1 where id = %2").arg(QString::number(target->id()), QString::number(item->id()));
        db->execute(sql.toStdString());
    }

    loadItems(source);
    loadItems(target);

    if (source->items().isEmpty())
    {
        erase(source);
    }
    else
    {
        calculate(source);
        update(source);
    }

    tx.commit();
}

QList<VoucherPtr> ShopService::savedVouchers()
{
    return all(QString("status = %1").arg(QString::number(Voucher::NOT_PAID)));
}

QList<VoucherPtr> ShopService::tempVouchers()
{
    return all(QString("status = %1").arg(QString::number(Voucher::TEMPORARY)));
}

QList<VoucherPtr> ShopService::paiedVouchers()
{
    SeasonService seasonSrv;
    SeasonPtr season = seasonSrv.active();

    return all(QString("status = %1 AND season = %2").arg(QString::number(Voucher::PAID), QString::number(season->id())));
}

QList<VoucherPtr> ShopService::vouchersForEet()
{
    return all(QString("status = %1 AND eetStatus <> %2 AND eetStatus <> %3")
               .arg(QString::number(Voucher::PAID), QString::number(Voucher::EET_SENT), QString::number(Voucher::EET_NOT_ENTERING)));
}

QList<ShopItemPtr> ShopService::allSellableItems()
{
    QList<QSharedPointer<ShopItem> > items;
    foreach (IPlugin *plugin, Context::instance().plugins()) {
        IService *srv = plugin->service<IService>();
        ISellableService *selSrv = dynamic_cast<ISellableService*>(srv);

        if (selSrv != NULL)
        {
            items.append(selSrv->shopItems());
        }
    }

    return items;
}

VoucherSum ShopService::unpaidSummary()
{
    Transaction tr;

    odb::database *db = Context::instance().db();
    typedef odb::query<VoucherSum> query;

    VoucherSum sum = db->query_value<VoucherSum>("status = " + query::_ref((int)Voucher::NOT_PAID));

    tr.commit();
    return sum;
}

VoucherSum ShopService::unsendEET()
{
    Transaction tr;

    odb::database *db = Context::instance().db();
    typedef odb::query<VoucherSum> query;

    VoucherSum sum = db->query_value<VoucherSum>("(eetStatus = " + query::_ref((int)Voucher::EET_FOR_SEND)
                                                 + " OR eetStatus = " + query::_ref((int)Voucher::EET_ERROR)
                                                 + ") AND status = " + query::_ref((int)Voucher::PAID));

    tr.commit();
    return sum;
}

QDecDouble ShopService::includeVat(QDecDouble price, Enums::VatType vatType)
{
    return price * ((vatRate(vatType) / 100) + QDecDouble(1));
}

QDecDouble ShopService::excludeVat(QDecDouble price, Enums::VatType vatType)
{
    return price / ((vatRate(vatType) / 100) + QDecDouble(1));
}

void ShopService::loadSettings()
{
    if (m_gs.isNull())
    {
        SettingsService settings("CORE");
        m_gs = settings.loadSettings<GlobalSettings>();
    }
}

QDecDouble ShopService::vatRate(Enums::VatType vatType)
{
    QDecDouble vatRate;

    loadSettings();
    switch (vatType) {
    case Enums::NONE:
        vatRate = 0;
        break;
    case Enums::HIGH:
        vatRate = m_gs->vatHigh();
        break;
    case Enums::FIRST_LOWER:
        vatRate = m_gs->vatFirstLower();
        break;
    case Enums::SECOND_LOWER:
        vatRate = m_gs->vatSecondLower();
        break;
    default:
        break;
    }

    return vatRate;
}

void ShopService::saveVoucher(VoucherPtr entity)
{
    SeasonService seasonSrv;
    SeasonPtr season = seasonSrv.active();
    entity->setSeason(season);

    Transaction tr;
    odb::database *db = Context::instance().db();

    addDateAndUser(entity, true);

    db->persist(entity);

    foreach (QSharedPointer<VoucherItem> item, entity->items()) {
        item->setVoucher(entity.toWeakRef());
        db->persist(item);
    }

    tr.commit();
}

void ShopService::updateVoucher(VoucherPtr entity)
{
    Transaction tr;
    odb::database *db = Context::instance().db();

    db->execute(QString("DELETE FROM VoucherItem WHERE voucher = %1").arg(entity->id()).toStdString());

    foreach (QSharedPointer<VoucherItem> item, entity->items()) {
        item->setVoucher(entity.toWeakRef());
        db->persist(item);
    }

    addDateAndUser(entity, false);

    db->update(entity);

    tr.commit();
}

void ShopService::eraseVoucher(VoucherPtr entity)
{
    Transaction tr;
    odb::database *db = Context::instance().db();

    db->execute(QString("DELETE FROM VoucherItem WHERE voucher = %1").arg(entity->id()).toStdString());
    db->erase(entity);

    tr.commit();
}
