#include "bridge.h"

#include <QBuffer>
#include <QByteArray>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase>
#include <QImage>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMarginsF>
#include <QPageLayout>
#include <QPageSize>
#include <QPainter>
#include <QPrinter>
#include <QPrinterInfo>
#include <QWidget>

namespace {

QJsonObject parsePayload(const QString &payloadJson) {
  const auto document = QJsonDocument::fromJson(payloadJson.toUtf8());
  if (!document.isObject()) {
    return {};
  }
  return document.object();
}

QString displayNameForPrinter(const QPrinterInfo &printer) {
  if (!printer.description().isEmpty()) {
    return printer.description();
  }
  return printer.printerName();
}

QImage imageFromDataUrl(const QString &dataUrl) {
  const auto commaIndex = dataUrl.indexOf(QLatin1Char(','));
  const auto payload = commaIndex >= 0 ? dataUrl.mid(commaIndex + 1) : dataUrl;
  QImage image;
  image.loadFromData(QByteArray::fromBase64(payload.toUtf8()));
  return image;
}

}  // namespace

DesktopBridge::DesktopBridge(QWidget *window, QObject *parent)
    : QObject(parent), window_(window), settings_("LabelNova", "LabelNova") {}

void DesktopBridge::setFrontendRoot(const QString &frontendRoot) {
  frontendRoot_ = frontendRoot;
}

QString DesktopBridge::windowMinimize() {
  if (window_) {
    window_->showMinimized();
  }
  return successResult();
}

QString DesktopBridge::windowMaximize() {
  if (window_) {
    if (window_->isMaximized()) {
      window_->showNormal();
    } else {
      window_->showMaximized();
    }
  }
  return successResult();
}

QString DesktopBridge::windowClose() {
  if (window_) {
    window_->close();
  }
  return successResult();
}

QString DesktopBridge::openProject(const QString &path) {
  QString targetPath = path;
  if (targetPath.isEmpty()) {
    const auto startDir = settings_.value("paths/projectDir", frontendRoot_).toString();
    targetPath = QFileDialog::getOpenFileName(
        window_,
        QStringLiteral("打开标签工程"),
        startDir,
        QStringLiteral("LabelTender Project (*.lblt *.json)"));
  }

  if (targetPath.isEmpty()) {
    return errorResult(QStringLiteral("cancelled"));
  }

  settings_.setValue("paths/projectDir", QFileInfo(targetPath).absolutePath());
  return filePayloadForPath(targetPath);
}

QString DesktopBridge::saveProject(const QString &payloadJson) {
  const auto payload = parsePayload(payloadJson);
  const auto suggestedName = payload.value("suggestedName").toString(QStringLiteral("未命名标签.lblt"));
  QString targetPath = payload.value("currentPath").toString();

  if (payload.value("saveAs").toBool() || targetPath.isEmpty()) {
    const auto startDir = settings_.value("paths/projectDir", frontendRoot_).toString();
    targetPath = QFileDialog::getSaveFileName(
        window_,
        QStringLiteral("保存标签工程"),
        startDir + QLatin1Char('/') + suggestedName,
        QStringLiteral("LabelTender Project (*.lblt)"));
  }

  if (targetPath.isEmpty()) {
    return errorResult(QStringLiteral("cancelled"));
  }

  targetPath = ensureFileExtension(targetPath, QStringLiteral(".lblt"));
  const auto writeError = writeBase64File(targetPath, payload.value("dataBase64").toString());
  if (!writeError.isEmpty()) {
    return errorResult(writeError);
  }

  settings_.setValue("paths/projectDir", QFileInfo(targetPath).absolutePath());
  QJsonObject result;
  result.insert("path", targetPath);
  result.insert("name", QFileInfo(targetPath).fileName());
  return successResult(result);
}

QString DesktopBridge::savePrintPdf(const QString &payloadJson) {
  const auto payload = parsePayload(payloadJson);
  const auto suggestedName = payload.value("suggestedName").toString(QStringLiteral("打印文件.pdf"));
  const auto startDir = settings_.value("paths/projectDir", frontendRoot_).toString();
  auto targetPath = QFileDialog::getSaveFileName(
      window_,
      QStringLiteral("导出打印 PDF"),
      startDir + QLatin1Char('/') + suggestedName,
      QStringLiteral("PDF File (*.pdf)"));

  if (targetPath.isEmpty()) {
    return errorResult(QStringLiteral("cancelled"));
  }

  targetPath = ensureFileExtension(targetPath, QStringLiteral(".pdf"));
  const auto writeError = writeBase64File(targetPath, payload.value("dataBase64").toString());
  if (!writeError.isEmpty()) {
    return errorResult(writeError);
  }

  settings_.setValue("paths/projectDir", QFileInfo(targetPath).absolutePath());
  return successResult(QJsonObject{{"path", targetPath}, {"name", QFileInfo(targetPath).fileName()}});
}

QString DesktopBridge::pickSpreadsheet() {
  const auto startDir = settings_.value("paths/sheetDir", frontendRoot_).toString();
  const auto targetPath = QFileDialog::getOpenFileName(
      window_,
      QStringLiteral("导入数据源"),
      startDir,
      QStringLiteral("Spreadsheet (*.xlsx *.xls *.csv)"));

  if (targetPath.isEmpty()) {
    return errorResult(QStringLiteral("cancelled"));
  }

  settings_.setValue("paths/sheetDir", QFileInfo(targetPath).absolutePath());
  return filePayloadForPath(targetPath);
}

QString DesktopBridge::readSpreadsheet(const QString &path) {
  if (path.isEmpty()) {
    return errorResult(QStringLiteral("missing spreadsheet path"));
  }
  return filePayloadForPath(path);
}

QString DesktopBridge::listLocalFonts() const {
  QFontDatabase fontDatabase;
  const auto families = fontDatabase.families();

  QJsonArray items;
  for (const auto &family : families) {
    if (family.isEmpty()) {
      continue;
    }
    items.append(QJsonObject{{"name", family}, {"value", family}});
  }

  return successResult(QJsonObject{{"fonts", items}});
}

QString DesktopBridge::listPrinters() const {
  const auto printers = QPrinterInfo::availablePrinters();
  const auto defaultPrinter = QPrinterInfo::defaultPrinter();

  QJsonArray items;
  for (const auto &printer : printers) {
    QJsonObject item;
    item.insert("name", printer.printerName());
    item.insert("displayName", displayNameForPrinter(printer));
    item.insert("description", printer.description());
    item.insert("location", printer.location());
    item.insert("isDefault", printer.printerName() == defaultPrinter.printerName());
    items.append(item);
  }

  return successResult(QJsonObject{{"printers", items}});
}

QString DesktopBridge::submitPrintJob(const QString &payloadJson) {
  const auto payload = parsePayload(payloadJson);
  const auto printerName = payload.value("printerName").toString();
  const auto pages = payload.value("pages").toArray();
  const auto widthMm = payload.value("pageWidthMm").toDouble();
  const auto heightMm = payload.value("pageHeightMm").toDouble();
  const auto jobName = payload.value("jobName").toString(QStringLiteral("标签打印"));

  if (printerName.isEmpty()) {
    return errorResult(QStringLiteral("未选择打印机"));
  }
  if (pages.isEmpty()) {
    return errorResult(QStringLiteral("没有可打印的页面"));
  }
  if (widthMm <= 0.0 || heightMm <= 0.0) {
    return errorResult(QStringLiteral("无效的页面尺寸"));
  }

  QPrinterInfo targetPrinter;
  for (const auto &printer : QPrinterInfo::availablePrinters()) {
    if (printer.printerName() == printerName) {
      targetPrinter = printer;
      break;
    }
  }

  if (targetPrinter.isNull()) {
    return errorResult(QStringLiteral("系统中未找到所选打印机"));
  }

  QPrinter printer(targetPrinter, QPrinter::HighResolution);
  printer.setDocName(jobName);
  printer.setFullPage(true);
  printer.setCopyCount(qMax(1, payload.value("copies").toInt(1)));

  const auto pageSize = QPageSize(QSizeF(widthMm, heightMm), QPageSize::Millimeter, QStringLiteral("labelnova-custom"));
  auto layout = printer.pageLayout();
  layout.setPageSize(pageSize);
  layout.setOrientation(widthMm > heightMm ? QPageLayout::Landscape : QPageLayout::Portrait);
  layout.setMode(QPageLayout::FullPageMode);
  layout.setMargins(QMarginsF(0, 0, 0, 0));
  printer.setPageLayout(layout);

  QPainter painter;
  if (!painter.begin(&printer)) {
    return errorResult(QStringLiteral("无法启动打印任务"));
  }

  for (int index = 0; index < pages.size(); ++index) {
    if (index > 0 && !printer.newPage()) {
      painter.end();
      return errorResult(QStringLiteral("创建打印页失败"));
    }

    const auto image = imageFromDataUrl(pages.at(index).toString());
    if (image.isNull()) {
      painter.end();
      return errorResult(QStringLiteral("打印页图像无效"));
    }

    const auto targetRect = QRectF(0, 0, printer.pageRect(QPrinter::DevicePixel).width(), printer.pageRect(QPrinter::DevicePixel).height());
    painter.drawImage(targetRect, image);
  }

  painter.end();

  return successResult(QJsonObject{
      {"printerName", printerName},
      {"printerDisplayName", displayNameForPrinter(targetPrinter)},
  });
}

QString DesktopBridge::readFileAsBase64(const QString &path) const {
  QFile file(path);
  if (!file.open(QIODevice::ReadOnly)) {
    return {};
  }
  return QString::fromLatin1(file.readAll().toBase64());
}

QString DesktopBridge::writeBase64File(const QString &path, const QString &dataBase64) const {
  QFile file(path);
  if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
    return QStringLiteral("无法写入目标文件");
  }

  const auto bytes = QByteArray::fromBase64(dataBase64.toUtf8());
  if (bytes.isEmpty() && !dataBase64.isEmpty()) {
    return QStringLiteral("文件内容编码无效");
  }

  if (file.write(bytes) < 0) {
    return QStringLiteral("写入文件失败");
  }

  return {};
}

QString DesktopBridge::filePayloadForPath(const QString &path) const {
  const auto dataBase64 = readFileAsBase64(path);
  if (dataBase64.isEmpty()) {
    return errorResult(QStringLiteral("无法读取文件"));
  }

  QJsonObject payload;
  payload.insert("path", path);
  payload.insert("name", QFileInfo(path).fileName());
  payload.insert("dataBase64", dataBase64);
  return successResult(payload);
}

QString DesktopBridge::errorResult(const QString &message) const {
  return QString::fromUtf8(QJsonDocument(QJsonObject{{"ok", false}, {"error", message}}).toJson(QJsonDocument::Compact));
}

QString DesktopBridge::successResult(const QJsonObject &payload) const {
  auto object = payload;
  object.insert("ok", true);
  return QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Compact));
}

QString DesktopBridge::ensureFileExtension(QString path, const QString &suffix) const {
  if (!path.endsWith(suffix, Qt::CaseInsensitive)) {
    path += suffix;
  }
  return path;
}
