#include "invoicemodel.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QSettings>
#include <QProcess>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QUrl>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDebug>

static const QStringList SEARCH_COLS = {
    "seller", "invoice_id", "issue_date", "receipt_date",
    "buyer_ref", "processed_by", "status", "type", "note", "folder"
};

// QSettings parses unquoted commas as a QStringList; toString() on those is empty.
static QStringList readCsvSetting(const QSettings &cfg, const QString &key, const QString &fallback = QString())
{
    const QVariant raw = cfg.value(key, fallback);
    QStringList list;
    if (raw.typeId() == QMetaType::QStringList) {
        list = raw.toStringList();
    } else {
        QString s = raw.toString();
        if (s.startsWith('"') && s.endsWith('"'))
            s = s.mid(1, s.length() - 2);
        list = s.split(',', Qt::SkipEmptyParts);
    }
    for (auto &f : list) f = f.trimmed();
    return list;
}

InvoiceModel::InvoiceModel(const QString &configName, QObject *parent)
    : QSqlQueryModel(parent)
{
    loadConfig(configName);
    if (!m_dbPath.isEmpty())
        openDatabase();
}

void InvoiceModel::loadConfig(const QString &configName)
{
    QString path = QDir::homePath() + "/.config/xroverview/" + configName;
    QSettings cfg(path, QSettings::IniFormat);

    m_invoicesDir = cfg.value("Invoices").toString();
    m_dbPath      = cfg.value("Invoice_Index").toString();

    m_mandantennr = cfg.value("Mandantennummer",    "1").toString();
    m_anwendernr  = cfg.value("Anwendernummer",     "1").toString();
    m_buchschl    = cfg.value("Buchungsschluessel", "5").toString();
    m_belegart    = cfg.value("Belegart",           "1").toString();

    m_mandatoryFields = readCsvSetting(cfg, "Mandatory_Fields");
    m_defaultColumns  = readCsvSetting(cfg, "Default_Columns",
        "seller,invoice_id,issue_date,receipt_date,buyer_ref,payable_amount,processed_by,leitweg_id,iban");

    QString fb = cfg.value("Filter_Button").toString();
    if (fb.startsWith('"') && fb.endsWith('"'))
        fb = fb.mid(1, fb.length() - 2);
    m_filterButtonLabel = fb.trimmed();

    QString cm = cfg.value("Context_Menu").toString();
    if (cm.startsWith('"') && cm.endsWith('"'))
        cm = cm.mid(1, cm.length() - 2);
    m_contextMenuLabel = cm.trimmed();
}

void InvoiceModel::openDatabase()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(m_dbPath);
    if (!db.open()) {
        qWarning() << "Cannot open" << m_dbPath << "—" << db.lastError().text();
        return;
    }
    m_dbOpen = true;
    emit dbOpenChanged();
    rebuildQuery();
}

void InvoiceModel::rebuildQuery()
{
    QString sql = "SELECT * FROM invoices";

    QStringList conds;
    if (!m_filter.isEmpty()) {
        for (const QString &col : SEARCH_COLS)
            conds << QString("CAST(%1 AS TEXT) LIKE ? ESCAPE '\\'").arg(col);
        sql += " WHERE " + conds.join(" OR ");
    }

    if (!m_sortColName.isEmpty() && m_columnNames.contains(m_sortColName)) {
        const QString dir = (m_sortOrder == Qt::AscendingOrder) ? "ASC" : "DESC";
        sql += " ORDER BY " + m_sortColName + " " + dir;
    } else {
        sql += " ORDER BY receipt_date DESC, issue_date DESC";
    }

    QSqlQuery q;
    if (!m_filter.isEmpty()) {
        // Escape LIKE wildcards so user input is treated as literal text.
        QString safe = m_filter;
        safe.replace(QLatin1Char('\\'), QStringLiteral("\\\\"));
        safe.replace(QLatin1Char('%'),  QStringLiteral("\\%"));
        safe.replace(QLatin1Char('_'),  QStringLiteral("\\_"));
        safe = QLatin1Char('%') + safe + QLatin1Char('%');
        q.prepare(sql);
        for (int i = 0; i < conds.size(); ++i)
            q.addBindValue(safe);
        q.exec();
    } else {
        q.exec(sql);
    }
    setQuery(std::move(q));
    while (canFetchMore(QModelIndex()))
        fetchMore(QModelIndex());

    m_columnNames.clear();
    for (int i = 0; i < columnCount(); ++i)
        m_columnNames << record().fieldName(i);
}

void InvoiceModel::setFilter(const QString &f)
{
    if (m_filter == f) return;
    m_filter = f;
    rebuildQuery();
    emit filterChanged();
}

bool InvoiceModel::isMandatoryColumn(int col) const
{
    if (col < 0 || col >= m_columnNames.size()) return false;
    return m_mandatoryFields.contains(m_columnNames.at(col));
}

bool InvoiceModel::isDefaultColumn(int col) const
{
    if (col < 0 || col >= m_columnNames.size()) return false;
    return m_defaultColumns.contains(m_columnNames.at(col));
}

QString InvoiceModel::columnName(int col) const
{
    if (col < 0 || col >= m_columnNames.size()) return {};
    return m_columnNames.at(col);
}

void InvoiceModel::openInvoicesDir() const
{
    QProcess::startDetached("xdg-open", {m_invoicesDir});
}

QString InvoiceModel::folderPath(int row) const
{
    QString folder = record(row).value("folder").toString();
    if (folder.isEmpty()) return {};
    if (QDir::isAbsolutePath(folder)) return folder;
    return m_invoicesDir + folder;
}

void InvoiceModel::refresh()
{
    rebuildQuery();
}

double InvoiceModel::selectedSum(const QVariantList &rows) const
{
    double total = 0.0;
    for (const QVariant &v : rows)
        total += record(v.toInt()).value(QStringLiteral("payable_amount")).toDouble();
    return total;
}

void InvoiceModel::sortByColumnName(const QString &colName, int order)
{
    m_sortColName = colName;
    m_sortOrder   = static_cast<Qt::SortOrder>(order);
    rebuildQuery();
}

static QString findScript(const QString &name)
{
    // Installed location: <datadir>/xroverview/sh/
    const QString installed = QStandardPaths::locate(
        QStandardPaths::GenericDataLocation,
        QStringLiteral("xroverview/sh/") + name);
    if (!installed.isEmpty()) return installed;

    // Development fallback: relative to the binary
    const QStringList bases = {
        QCoreApplication::applicationDirPath() + "/..",
        QCoreApplication::applicationDirPath()
    };
    for (const QString &base : bases) {
        QString p = QFileInfo(base + "/sh/" + name).absoluteFilePath();
        if (QFile::exists(p)) return p;
    }
    return {};
}

// Returns empty string on success, error description on failure.
static QString runScript(const QString &script, const QStringList &args, QFile &out)
{
    QProcess proc;
    proc.start(QStringLiteral("bash"), QStringList() << script << args);
    if (!proc.waitForFinished(30000)) {
        proc.kill();
        return QStringLiteral("Timeout waiting for %1").arg(QFileInfo(script).fileName());
    }
    const QByteArray err = proc.readAllStandardError();
    if (proc.exitCode() != 0) {
        return err.isEmpty()
            ? QStringLiteral("%1 exited with code %2")
                  .arg(QFileInfo(script).fileName()).arg(proc.exitCode())
            : QString::fromLocal8Bit(err).trimmed();
    }
    out.write(proc.readAllStandardOutput());
    return {};
}

bool InvoiceModel::exportRows(const QVariantList &rows, const QString &destUrl, const QString &format)
{
    QString destPath = QUrl(destUrl).toLocalFile();
    if (destPath.isEmpty()) destPath = destUrl;

    QFile out(destPath);
    if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
        emit exportFailed(tr("Cannot write to:\n%1\n\nCheck file permissions.").arg(destPath));
        return false;
    }

    const QString scriptName = (format == QLatin1String("xfinanz"))
        ? QStringLiteral("xfinanz_writer.sh")
        : (format == QLatin1String("csv"))
            ? QStringLiteral("csv_writer.sh")
            : QStringLiteral("sasso_task_writer.sh");

    QString script = findScript(scriptName);
    if (script.isEmpty()) {
        const QString searchPath = QStandardPaths::locate(
            QStandardPaths::GenericDataLocation, QStringLiteral("xroverview/sh"));
        emit exportFailed(tr("Export script not found: %1\n\nSearched in:\n%2")
            .arg(scriptName, searchPath.isEmpty()
                ? QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join("\n")
                : searchPath));
        return false;
    }

    if (!QFileInfo(script).isExecutable()) {
        emit exportFailed(tr("Export script is not executable:\n%1").arg(script));
        return false;
    }

    if (format == QLatin1String("csv")) {
        const QString err = runScript(script, {"--header"}, out);
        if (!err.isEmpty()) {
            emit exportFailed(tr("CSV header failed:\n%1").arg(err));
            return false;
        }
    }

    for (const QVariant &v : rows) {
        QSqlRecord rec = record(v.toInt());
        auto val = [&](const char *f) { return rec.value(QLatin1String(f)).toString(); };

        // receipt_date "YYYY-MM-DD" → "DDMMYYYY"
        QString date = val("receipt_date");
        QString faelterm;
        if (date.size() == 10)
            faelterm = date.mid(8, 2) + date.mid(5, 2) + date.left(4);

        // buyer_ref "02000.52000" → "0200052000"
        QString hhst = val("buyer_ref");
        hhst.remove('.');

        QStringList args = {
            "--beleg",              val("id"),
            "--art",                QStringLiteral("1"),
            "--mandantennummer",    m_mandantennr,
            "--anwendernummer",     m_anwendernr,
            "--haushaltsstelle",    hhst,
            "--buchungsschluessel", m_buchschl,
            "--betrag",             QString::number(rec.value("payable_amount").toDouble(), 'f', 2),
            "--belegart",           m_belegart,
        };

        auto opt = [&](const char *flag, const QString &s) {
            if (!s.isEmpty()) args << QLatin1String(flag) << s;
        };
        opt("--faelterm",      faelterm);
        opt("--name",          val("seller"));
        opt("--zahlungsgrund", val("invoice_id"));
        opt("--rechnungs_nr",  val("invoice_id"));
        opt("--iban",          val("iban"));
        opt("--bic",           val("bic"));
        opt("--strasse",       val("seller_street"));
        opt("--postleitzahl",  val("seller_zip"));
        opt("--ort",           val("seller_city"));
        double tax = rec.value("tax_amount").toDouble();
        double vat = rec.value("vat_rate").toDouble();
        if (tax > 0) opt("--mwst_betrag", QString::number(tax, 'f', 2));
        if (vat > 0) opt("--mwst_proz",   QString::number(vat, 'f', 2));
        if (format == QLatin1String("csv")) {
            opt("--issue_date",   val("issue_date"));
            opt("--receipt_date", val("receipt_date"));
        }

        const QString err = runScript(script, args, out);
        if (!err.isEmpty()) {
            emit exportFailed(tr("Export failed for invoice %1:\n%2")
                .arg(val("id"), err));
            return false;
        }
    }
    return true;
}
