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

#include "settings/shopsettings.h"

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

ShopService::ShopService()
{
}

VoucherPtr ShopService::createVoucher()
{
    QSharedPointer<Voucher> voucher(new Voucher);
    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());

    voucher->addItem(vItem);

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

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

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

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

        QDecDouble priceWitouthWat = item->priceWitouthVat();
        switch (item->vatType()) {
        case Enums::NONE:
            voucher->setPriceNoVat(voucher->priceNoVat() + priceWitouthWat);
            break;
        case Enums::HIGH:
            voucher->setPriceVatHigh(voucher->priceVatHigh() + priceWitouthWat);
            break;
        case Enums::FIRST_LOWER:
            voucher->setPriceVatFirstLower(voucher->priceVatFirstLower() + priceWitouthWat);
            break;
        case Enums::SECOND_LOWER:
            voucher->setPriceVatSecondLower(voucher->priceVatSecondLower() + priceWitouthWat);
            break;
        default:
            break;
        }

        total += item->price();
    }

    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);
}

void ShopService::calculateItem(VoucherItemPtr item)
{
    loadSettings();
    if (m_gs->vatPayer())
    {
        item->setVatRate(vatRate(item->vatType()));
        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;

    NumberSeriesPtr numSer = srvNs.nextForPlugin("SHOP");
    QString numSerStr;
    numSerStr.sprintf("%s%05d", numSer->prefix().toStdString().c_str(), numSer->lastNumber());

    voucher->setNumSer(numSerStr);
    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)
{
    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());

    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;
}

void ShopService::setEetOnline(bool online)
{
    EetSender::m_online = online;
}

bool ShopService::isEetOnline()
{
    return EetSender::m_online;
}

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

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()
{
    return all(QString("status = %1").arg(QString::number(Voucher::PAID)));
}

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;
}

QDecDouble ShopService::includeVat(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)
{
    Transaction tr;
    odb::database *db = Context::instance().db();

    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);
    }

    db->update(entity);

    tr.commit();
}
