#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <solar/logger/database.hpp>
#include <spdlog/spdlog.h>
#include <sstream>
#include <stdexcept>
#include <util/date.hpp>

std::string ToSqlInsertStatement(ZeverRow const & row)
{
    std::stringstream ss;
    ss << "INSERT INTO ZeverLogs(Date,TimeUtc,CurrentWatts,TotalWatts) VALUES(" << '\''
       << Util::GetSqliteDate(row.epochTime) << "'," << '\'' << Util::GetSqliteUtcTime(row.epochTime) << "',"
       << row.watt << ',' << static_cast<std::int64_t>(round(row.kilowattPerHour * 1000)) << ");";

    return ss.str();
}

std::string ToSqlInsertStatement(EnvoyRow const & row)
{
    std::stringstream ss;
    ss << "INSERT INTO EnvoyLogs(Date,TimeUtc,CurrentWatts,TotalWatts,Inverters) VALUES(" << '\''
       << Util::GetSqliteDate(row.epochTime) << "'," << '\'' << Util::GetSqliteUtcTime(row.epochTime) << "',"
       << row.currentWatt << ',' << row.lifetimeWattHour << ',' << row.inverterCount << ");";

    return ss.str();
}

std::string ToSqlUpsertStatement(ZeverRow const & row)
{
    std::stringstream ss;
    ss << "INSERT INTO ZeverSummary(Date,TotalWatts) VALUES(" << '\'' << Util::GetSqliteDate(row.epochTime) << "',"
       << std::fixed << std::setprecision(2) << static_cast<std::int64_t>(round(row.kilowattPerHour * 1000)) << ')'
       << "ON CONFLICT(Date) DO UPDATE SET TotalWatts = excluded.TotalWatts WHERE excluded.TotalWatts > ZeverSummary.TotalWatts;";

    return ss.str();
}

bool Database::Insert(ZeverRow & row)
{
    std::stringstream transaction;
    transaction << "BEGIN TRANSACTION;" << ToSqlInsertStatement(row) << ToSqlUpsertStatement(row) << "COMMIT;";

    if(sqlite3_exec(connectionPtr, transaction.str().c_str(), nullptr, nullptr, nullptr) != SQLITE_OK)
    {
        spdlog::error("Failed to insert Zeverlution record into SQLite database: {}", sqlite3_errmsg(connectionPtr));
        return false;
    }
    return true;
}

bool Database::Insert(EnvoyRow & row)
{
    if(sqlite3_exec(connectionPtr, ToSqlInsertStatement(row).c_str(), nullptr, nullptr, nullptr) != SQLITE_OK)
    {
        spdlog::error("Failed to insert Envoy record into SQLite database: {}", sqlite3_errmsg(connectionPtr));
        return false;
    }
    return true;
}

Database::Database(std::string const & databasePath) : connectionPtr(nullptr)
{
    if(sqlite3_open(databasePath.c_str(), &connectionPtr) != SQLITE_OK)
    {
        throw std::runtime_error("Error opening SQLite3 database");
    }

    sqlite3_extended_result_codes(connectionPtr, 1);
}

Database::~Database()
{
    if(connectionPtr)
    {
        sqlite3_close(connectionPtr);
    }
}
