// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-energy-plugin-nymea.
*
* nymea-energy-plugin-nymea.s free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-energy-plugin-nymea.s distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-energy-plugin-nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "spotmarketdataproviderawattar.h"
#include "../plugininfo.h"

#include <QUrlQuery>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonParseError>

SpotMarketDataProviderAwattar::SpotMarketDataProviderAwattar(QNetworkAccessManager *networkManager, AwattarCountry country, QObject *parent)
    : SpotMarketDataProvider{networkManager, parent}
{
    QString name;
    QUrl website;

    switch (country) {
    case AwattarCountryAustria:
        name = "aWATTar AT";
        m_country = QLocale::Austria;
        website = QUrl("https://www.awattar.at");
        break;
    case AwattarCountryGermany:
        name = "aWATTar DE";
        m_country = QLocale::Germany;
        website = QUrl("https://www.awattar.de");
        break;
    }

    // Insert the provider based on the country
    m_info = SpotMarketProviderInfo(SpotMarketDataProviderAwattar::providerId(), name, m_country, website);

    m_refreshTimer.setInterval(60000);
    m_refreshTimer.setSingleShot(false);
    connect(&m_refreshTimer, &QTimer::timeout, this, &SpotMarketDataProviderAwattar::onRefreshTimout);

    evaluateAvailable();
}

QUuid SpotMarketDataProviderAwattar::providerId() const
{
    switch (m_country) {
    case QLocale::Austria:
        return QUuid("5196b3cc-b2ee-46d6-b63a-7af2cf70ba67");
        break;
    case QLocale::Germany:
        return QUuid("0ca6ad88-e243-438d-a0f8-986cecf61834");
        break;
    default:
        break;
    }

    return QUuid();
}

QLocale::Country SpotMarketDataProviderAwattar::country() const
{
    return m_country;
}

void SpotMarketDataProviderAwattar::enable()
{
    m_refreshTimer.start();

    if (!m_enabled) {
        m_enabled = true;
        emit enabledChanged(m_enabled);
    }

    ScoreEntries cachedEntries = loadCachedDataEntries();
    // CLean up already passed cached scores
    foreach (const ScoreEntry &score, cachedEntries) {
        if (score.endDateTime() < QDateTime::currentDateTime()) {
            cachedEntries.removeAll(score);
        }
    }

    m_scoreEntries = cachedEntries;
    qCDebug(dcNymeaEnergy()) << this << "having" << m_scoreEntries.count() << "scores available from cache.";
    emit scoreEntriesChanged(m_scoreEntries);

    onRefreshTimout();
}

void SpotMarketDataProviderAwattar::disable()
{
    m_refreshTimer.stop();
    if (m_enabled) {
        m_enabled = false;
        emit enabledChanged(m_enabled);
    }
    evaluateAvailable();
}

void SpotMarketDataProviderAwattar::refreshData()
{
    QUrl requestUrl;
    switch (m_country) {
    case QLocale::Austria:
        requestUrl = QUrl("https://api.awattar.at/v1/marketdata");
        break;
    case QLocale::Germany:
        requestUrl = QUrl("https://api.awattar.de/v1/marketdata");
        break;
    default:
        break;
    }

    //    QDateTime startTime = QDateTime::currentDateTime().addDays(-1);
    //    QDateTime endTime = QDateTime::currentDateTime().addDays(1);

    //    QUrlQuery query;
    //    query.addQueryItem("start", QString::number(startTime.toMSecsSinceEpoch()));
    //    query.addQueryItem("end", QString::number(endTime.toMSecsSinceEpoch()));
    //    requestUrl.setQuery(query);
    //    qCDebug(dcNymeaEnergy()) << m_info << "refresh data from" << startTime.toString("dd.MM.yyyy hh:mm") << "until" << endTime.toString("dd.MM.yyyy hh:mm");

    qCDebug(dcNymeaEnergy()) << this << "refresh data";
    QNetworkReply *reply = m_networkManager->get(QNetworkRequest(requestUrl));
    connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
    connect(reply, &QNetworkReply::finished, this, [this, reply](){
        if (reply->error() != QNetworkReply::NoError) {
            qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. Reply finished with error:" << reply->errorString();
            evaluateAvailable();
            return;
        }

        int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        if (status != 200) {
            qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. HTTP returned status:" << status;
            evaluateAvailable();
            return;
        }

        QJsonParseError error;
        QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error);
        if (error.error != QJsonParseError::NoError) {
            qCWarning(dcNymeaEnergy()) << this << "Failed to refresh data. The payload contains invalid JSON data:" << error.errorString();
            evaluateAvailable();
            return;
        }

        m_lastRefresh = QDateTime::currentDateTime();

        // Parse data
        QVariantList dataList = jsonDoc.toVariant().toMap().value("data").toList();
        ScoreEntries scoreEntries;
        foreach (const QVariant &dataVariant, dataList) {
            QVariantMap dataMap = dataVariant.toMap();
            double currentMarketPrice = dataMap.value("marketprice").toDouble();
            ScoreEntry entry;
            entry.setStartDateTime(QDateTime::fromMSecsSinceEpoch(dataMap.value("start_timestamp").toULongLong()));
            entry.setEndDateTime(QDateTime::fromMSecsSinceEpoch(dataMap.value("end_timestamp").toULongLong()));
            entry.setValue(currentMarketPrice);
            entry.setWeighting(0); // We let the manager weight the scores depending on the selected time window
            scoreEntries.append(entry);

            // qCDebug(dcNymeaEnergy()) << "Parsed spot market data:" << entry.startDateTime().toUTC().toString() << entry.startDateTime().toString() << "-->" << entry.value();
        }

        m_scoreEntries = scoreEntries;
        cacheDataEntries(m_scoreEntries);
        evaluateAvailable();
        emit scoreEntriesChanged(m_scoreEntries);
    });
}

void SpotMarketDataProviderAwattar::onRefreshTimout()
{
    // If the last refresh is longer ago than the an hour, refresh...
    if (m_lastRefresh < QDateTime::currentDateTime().addSecs(-3600)) {
        refreshData();
    }/* else {
        qCDebug(dcNymeaEnergy()) << this << "no need to refresh. Last refresh was" << m_lastRefresh.toString("dd.MM.yyyy hh:mm");
    }*/

    evaluateAvailable();
}

void SpotMarketDataProviderAwattar::evaluateAvailable()
{
    bool available = false;
    int availableFutureSchedules = m_scoreEntries.availableFutureSchedules(QDateTime::currentDateTime());
    // Note: in the worst case we have only 10 or 11 schedules into the future.
    // The data will be refreshed every day at 14:00 for the next 24 hours.
    // If we refresh the data the first time after 13:00 only 10 or 11 hours in the
    // future are available, depending on current daylight saving.

    if (m_enabled && availableFutureSchedules >= 10)
        available = true;

    if (m_available == available)
        return;

    if (available) {
        qCDebug(dcNymeaEnergy()) << this << "is now available and has" << availableFutureSchedules << "schedules into the future until" << m_scoreEntries.last().endDateTime().toString("dd.MM.yyyy hh:mm");
    } else {
        qCDebug(dcNymeaEnergy()) << this << "is not available any more.";
    }
    m_available = available;
    emit availableChanged(m_available);
}
