#include "ruledao.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDateTime>

bool RuleCondition::match(const QString& text) const
{
    QString haystack = caseSensitive ? text : text.toLower();
    QString needle = caseSensitive ? value : value.toLower();

    switch (op) {
        case Contains:       return haystack.contains(needle);
        case NotContains:    return !haystack.contains(needle);
        case Equals:         return haystack == needle;
        case NotEquals:      return haystack != needle;
        case StartsWith:     return haystack.startsWith(needle);
        case EndsWith:       return haystack.endsWith(needle);
        case Regex:          return QRegularExpression(value, caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption).match(text).hasMatch();
        default:             return false;
    }
}

QJsonObject RuleCondition::toJson() const
{
    QJsonObject obj;
    obj["field"] = static_cast<int>(field);
    obj["op"] = static_cast<int>(op);
    obj["value"] = value;
    obj["caseSensitive"] = caseSensitive;
    return obj;
}

RuleCondition RuleCondition::fromJson(const QJsonObject& obj)
{
    RuleCondition cond;
    cond.field = static_cast<Field>(obj["field"].toInt());
    cond.op = static_cast<Operator>(obj["op"].toInt());
    cond.value = obj["value"].toString();
    cond.caseSensitive = obj["caseSensitive"].toBool();
    return cond;
}

QJsonObject RuleAction::toJson() const
{
    QJsonObject obj;
    obj["type"] = static_cast<int>(type);
    obj["value"] = value;
    return obj;
}

RuleAction RuleAction::fromJson(const QJsonObject& obj)
{
    RuleAction act;
    act.type = static_cast<Type>(obj["type"].toInt());
    act.value = obj["value"].toString();
    return act;
}

QJsonObject Rule::toJson() const
{
    QJsonObject obj;
    obj["id"] = static_cast<qint64>(id);
    obj["accountId"] = accountId >= 0 ? static_cast<qint64>(accountId) : QJsonValue::Null;
    obj["name"] = name;
    obj["description"] = description;
    obj["enabled"] = enabled;
    obj["priority"] = priority;
    obj["matchAll"] = matchAll;

    QJsonArray condArray;
    for (const auto& c : conditions) condArray.append(c.toJson());
    obj["conditions"] = condArray;

    QJsonArray actArray;
    for (const auto& a : actions) actArray.append(a.toJson());
    obj["actions"] = actArray;

    obj["createdAt"] = createdAt.toString(Qt::ISODate);
    obj["updatedAt"] = updatedAt.toString(Qt::ISODate);
    obj["lastRun"] = lastRun.isValid() ? QJsonValue(lastRun.toString(Qt::ISODate)) : QJsonValue::Null;
    obj["runCount"] = runCount;
    return obj;
}

Rule Rule::fromJson(const QJsonObject& obj)
{
    Rule rule;
    rule.id = obj["id"].toVariant().toLongLong();
    rule.accountId = obj["accountId"].isNull() ? -1 : obj["accountId"].toVariant().toLongLong();
    rule.name = obj["name"].toString();
    rule.description = obj["description"].toString();
    rule.enabled = obj["enabled"].toBool();
    rule.priority = obj["priority"].toInt();
    rule.matchAll = obj["matchAll"].toBool();

    QJsonArray condArray = obj["conditions"].toArray();
    for (const auto& v : condArray) rule.conditions.append(RuleCondition::fromJson(v.toObject()));

    QJsonArray actArray = obj["actions"].toArray();
    for (const auto& v : actArray) rule.actions.append(RuleAction::fromJson(v.toObject()));

    rule.createdAt = QDateTime::fromString(obj["createdAt"].toString(), Qt::ISODate);
    rule.updatedAt = QDateTime::fromString(obj["updatedAt"].toString(), Qt::ISODate);
    rule.lastRun = obj["lastRun"].isNull() ? QDateTime() : QDateTime::fromString(obj["lastRun"].toString(), Qt::ISODate);
    rule.runCount = obj["runCount"].toVariant().toLongLong();
    return rule;
}

bool RuleDao::insert(Rule& rule)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "INSERT INTO Rule (accountId, name, description, enabled, priority, matchAll, conditions, actions, createdAt, updatedAt, lastRun, runCount) "
        "VALUES (:accountId, :name, :description, :enabled, :priority, :matchAll, :conditions, :actions, :createdAt, :updatedAt, :lastRun, :runCount)"
    );
    query.bindValue(":accountId", rule.accountId >= 0 ? rule.accountId : QVariant());
    query.bindValue(":name", rule.name);
    query.bindValue(":description", rule.description);
    query.bindValue(":enabled", rule.enabled ? 1 : 0);
    query.bindValue(":priority", rule.priority);
    query.bindValue(":matchAll", rule.matchAll ? 1 : 0);

    QJsonObject json = rule.toJson();
    QJsonArray condArray = json["conditions"].toArray();
    QJsonArray actArray = json["actions"].toArray();
    QJsonDocument condDoc(condArray);
    QJsonDocument actDoc(actArray);
    query.bindValue(":conditions", condDoc.toJson(QJsonDocument::Compact));
    query.bindValue(":actions", actDoc.toJson(QJsonDocument::Compact));

    query.bindValue(":createdAt", QDateTime::currentDateTime());
    query.bindValue(":updatedAt", QDateTime::currentDateTime());
    query.bindValue(":lastRun", QVariant());
    query.bindValue(":runCount", 0);

    if (!query.exec()) {
        qWarning() << "Failed to insert rule:" << query.lastError().text();
        return false;
    }
    rule.id = query.lastInsertId().toLongLong();
    return true;
}

bool RuleDao::update(const Rule& rule)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "UPDATE Rule SET "
        "accountId = :accountId, "
        "name = :name, "
        "description = :description, "
        "enabled = :enabled, "
        "priority = :priority, "
        "matchAll = :matchAll, "
        "conditions = :conditions, "
        "actions = :actions, "
        "updatedAt = :updatedAt "
        "WHERE id = :id"
    );
    query.bindValue(":id", rule.id);
    query.bindValue(":accountId", rule.accountId >= 0 ? rule.accountId : QVariant());
    query.bindValue(":name", rule.name);
    query.bindValue(":description", rule.description);
    query.bindValue(":enabled", rule.enabled ? 1 : 0);
    query.bindValue(":priority", rule.priority);
    query.bindValue(":matchAll", rule.matchAll ? 1 : 0);

    QJsonObject json = rule.toJson();
    QJsonArray condArray = json["conditions"].toArray();
    QJsonArray actArray = json["actions"].toArray();
    QJsonDocument condDoc(condArray);
    QJsonDocument actDoc(actArray);
    query.bindValue(":conditions", condDoc.toJson(QJsonDocument::Compact));
    query.bindValue(":actions", actDoc.toJson(QJsonDocument::Compact));

    query.bindValue(":updatedAt", QDateTime::currentDateTime());

    if (!query.exec()) {
        qWarning() << "Failed to update rule:" << query.lastError().text();
        return false;
    }
    return query.numRowsAffected() > 0;
}

bool RuleDao::remove(qint64 id)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("DELETE FROM Rule WHERE id = :id");
    query.bindValue(":id", id);

    if (!query.exec()) {
        qWarning() << "Failed to delete rule:" << query.lastError().text();
        return false;
    }
    return query.numRowsAffected() > 0;
}

std::optional<Rule> RuleDao::findById(qint64 id)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("SELECT id, accountId, name, description, enabled, priority, matchAll, conditions, actions, createdAt, updatedAt, lastRun, runCount FROM Rule WHERE id = :id");
    query.bindValue(":id", id);

    if (!query.exec() || !query.next()) {
        return std::nullopt;
    }

    Rule rule;
    rule.id = query.value(0).toLongLong();
    rule.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
    rule.name = query.value(2).toString();
    rule.description = query.value(3).toString();
    rule.enabled = query.value(4).toBool();
    rule.priority = query.value(5).toInt();
    rule.matchAll = query.value(6).toBool();

    QJsonDocument condDoc = QJsonDocument::fromJson(query.value(7).toByteArray());
    QJsonArray condArray = condDoc.array();
    for (const auto& v : condArray) rule.conditions.append(RuleCondition::fromJson(v.toObject()));

    QJsonDocument actDoc = QJsonDocument::fromJson(query.value(8).toByteArray());
    QJsonArray actArray = actDoc.array();
    for (const auto& v : actArray) rule.actions.append(RuleAction::fromJson(v.toObject()));

    rule.createdAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
    rule.updatedAt = QDateTime::fromString(query.value(10).toString(), Qt::ISODate);
    rule.lastRun = query.value(11).isNull() ? QDateTime() : QDateTime::fromString(query.value(11).toString(), Qt::ISODate);
    rule.runCount = query.value(12).toLongLong();
    return rule;
}

QVector<Rule> RuleDao::findByAccount(qint64 accountId)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    if (accountId >= 0) {
        query.prepare("SELECT id, accountId, name, description, enabled, priority, matchAll, conditions, actions, createdAt, updatedAt, lastRun, runCount FROM Rule WHERE accountId = :accountId OR accountId IS NULL ORDER BY priority, name");
        query.bindValue(":accountId", accountId);
    } else {
        query.prepare("SELECT id, accountId, name, description, enabled, priority, matchAll, conditions, actions, createdAt, updatedAt, lastRun, runCount FROM Rule WHERE accountId IS NULL ORDER BY priority, name");
    }

    QVector<Rule> rules;
    if (query.exec()) {
        while (query.next()) {
            Rule rule;
            rule.id = query.value(0).toLongLong();
            rule.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
            rule.name = query.value(2).toString();
            rule.description = query.value(3).toString();
            rule.enabled = query.value(4).toBool();
            rule.priority = query.value(5).toInt();
            rule.matchAll = query.value(6).toBool();

            QJsonDocument condDoc = QJsonDocument::fromJson(query.value(7).toByteArray());
            QJsonArray condArray = condDoc.array();
            for (const auto& v : condArray) rule.conditions.append(RuleCondition::fromJson(v.toObject()));

            QJsonDocument actDoc = QJsonDocument::fromJson(query.value(8).toByteArray());
            QJsonArray actArray = actDoc.array();
            for (const auto& v : actArray) rule.actions.append(RuleAction::fromJson(v.toObject()));

            rule.createdAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
            rule.updatedAt = QDateTime::fromString(query.value(10).toString(), Qt::ISODate);
            rule.lastRun = query.value(11).isNull() ? QDateTime() : QDateTime::fromString(query.value(11).toString(), Qt::ISODate);
            rule.runCount = query.value(12).toLongLong();
            rules.append(rule);
        }
    }
    return rules;
}

QVector<Rule> RuleDao::findGlobal()
{
    return findByAccount(-1);
}

QVector<Rule> RuleDao::findAllEnabled()
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("SELECT id, accountId, name, description, enabled, priority, matchAll, conditions, actions, createdAt, updatedAt, lastRun, runCount FROM Rule WHERE enabled = 1 ORDER BY priority, name");

    QVector<Rule> rules;
    if (query.exec()) {
        while (query.next()) {
            Rule rule;
            rule.id = query.value(0).toLongLong();
            rule.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
            rule.name = query.value(2).toString();
            rule.description = query.value(3).toString();
            rule.enabled = query.value(4).toBool();
            rule.priority = query.value(5).toInt();
            rule.matchAll = query.value(6).toBool();

            QJsonDocument condDoc = QJsonDocument::fromJson(query.value(7).toByteArray());
            QJsonArray condArray = condDoc.array();
            for (const auto& v : condArray) rule.conditions.append(RuleCondition::fromJson(v.toObject()));

            QJsonDocument actDoc = QJsonDocument::fromJson(query.value(8).toByteArray());
            QJsonArray actArray = actDoc.array();
            for (const auto& v : actArray) rule.actions.append(RuleAction::fromJson(v.toObject()));

            rule.createdAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
            rule.updatedAt = QDateTime::fromString(query.value(10).toString(), Qt::ISODate);
            rule.lastRun = query.value(11).isNull() ? QDateTime() : QDateTime::fromString(query.value(11).toString(), Qt::ISODate);
            rule.runCount = query.value(12).toLongLong();
            rules.append(rule);
        }
    }
    return rules;
}

bool RuleDao::setEnabled(qint64 id, bool enabled)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("UPDATE Rule SET enabled = :enabled, updatedAt = :updatedAt WHERE id = :id");
    query.bindValue(":id", id);
    query.bindValue(":enabled", enabled ? 1 : 0);
    query.bindValue(":updatedAt", QDateTime::currentDateTime());

    if (!query.exec()) {
        qWarning() << "Failed to set rule enabled:" << query.lastError().text();
        return false;
    }
    return query.numRowsAffected() > 0;
}

bool RuleDao::updateLastRun(qint64 id, const QDateTime& when, qint64 runCount)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("UPDATE Rule SET lastRun = :lastRun, runCount = :runCount WHERE id = :id");
    query.bindValue(":id", id);
    query.bindValue(":lastRun", when);
    query.bindValue(":runCount", runCount);

    if (!query.exec()) {
        qWarning() << "Failed to update rule last run:" << query.lastError().text();
        return false;
    }
    return query.numRowsAffected() > 0;
}