Pith - wedge
#include "WedgeNetwork.h"
#include <QByteArray>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include "WedgeConfig.h"

NetworkManager::NetworkManager(QObject *parent)
: QObject(parent), socket(nullptr), server(nullptr), m_connected(false),
adbTracker(nullptr), m_metaSeen(false), m_syncPending(false), m_adbForwarded(false),
m_autoMerge(ConfigManager::loadNote().autoMerge), m_peerAutoMerge(false),
m_peerLastModified(0) {
	dialTimer = new QTimer(this);
	dialTimer->setSingleShot(true);
	dialTimer->setInterval(5000);
	connect(dialTimer, &QTimer::timeout, this, &NetworkManager::handleDialTimeout);

	idleTimer = new QTimer(this);
	idleTimer->setSingleShot(true);
	idleTimer->setInterval(3000);
	connect(idleTimer, &QTimer::timeout, this, &NetworkManager::handleIdleTimeout);
}

NetworkManager::~NetworkManager() {
	disconnectFromPeer();
	teardownAdb();
}

void NetworkManager::startListener() {
	if (server) return;
	server = new QTcpServer(this);
	connect(server, &QTcpServer::newConnection, this, &NetworkManager::handleNewConnection);
	server->listen(QHostAddress::Any, PORT_HOST);
	startAdbTracker();
}

// adb tunnel on connect
void NetworkManager::startAdbTracker() {
	adbTracker = new QProcess(this);
	connect(adbTracker, &QProcess::readyReadStandardOutput,
		this, &NetworkManager::handleAdbTrackerOutput);
	connect(adbTracker, &QProcess::finished, this, [this]() {
		QTimer::singleShot(2000, this, [this]() {
			adbTracker->start("adb", QStringList() << "track-devices");
		});
	});
	adbTracker->start("adb", QStringList() << "track-devices");
}

void NetworkManager::handleAdbTrackerOutput() {
	const QString chunk = QString::fromUtf8(adbTracker->readAllStandardOutput());
	const bool devicePresent = chunk.contains("\tdevice");
	if (devicePresent && !m_adbForwarded) {
		runAdbTunnels();
		emit statusChanged("Idle", false);
		if (m_autoMerge && !m_connected &&
			ConfigManager::loadNote().networkRoute == 0) { dial(Route::USB); }
	} else if (!devicePresent) {
		m_adbForwarded = false;
	}
}

void NetworkManager::requestSync(Route route) {
	if (m_metaSeen && socket && socket->state() == QAbstractSocket::ConnectedState) {
		emit linkReady();
		return;
	}
	m_syncPending = true;
	if (route == Route::USB && !m_adbForwarded) { runAdbTunnels(); }
	dial(route);
}

void NetworkManager::runAdbTunnels() {
	emit statusChanged("ADB Tunnel", false);
	QString phonePort = QString("tcp:%1").arg(PORT_PHONE);
	QString hostPort = QString("tcp:%1").arg(PORT_HOST);
	QProcess::execute("adb", QStringList() << "forward" << phonePort << phonePort);
	QProcess::execute("adb", QStringList() << "reverse" << hostPort << hostPort);
	m_adbForwarded = true;
}

void NetworkManager::teardownAdb() {
	if (!m_adbForwarded) return;
	m_adbForwarded = false;
	QProcess::execute("adb", QStringList() << "forward" << "--remove-all");
	QProcess::execute("adb", QStringList() << "reverse" << "--remove-all");
}

void NetworkManager::dial(Route route) {
	if (socket) {
		socket->disconnect(this);
		socket->abort();
		socket->deleteLater();
		socket = nullptr;
	}
	m_metaSeen = false;

	socket = new QTcpSocket(this);
	attachSocket(socket);

	emit statusChanged("Connecting", false);

	dialTimer->start();

	QString targetHost = (route == Route::USB) ? "127.0.0.1" : ConfigManager::loadNote().ipAddress;
	socket->connectToHost(targetHost, PORT_PHONE);
}

void NetworkManager::attachSocket(QTcpSocket *s) {
	connect(s, &QTcpSocket::connected, this, &NetworkManager::handleConnected);
	connect(s, &QTcpSocket::disconnected, this, &NetworkManager::handleDisconnected);
	connect(s, &QTcpSocket::readyRead, this, &NetworkManager::handleReadyRead);
	connect(s, &QTcpSocket::errorOccurred, this, &NetworkManager::handleSocketError);
}

void NetworkManager::handleNewConnection() {
	if (!server) return;

	QTcpSocket *incoming = server->nextPendingConnection();
	if (socket) { incoming->abort(); incoming->deleteLater(); return; }

	socket = incoming;
	m_metaSeen = false;
	attachSocket(socket);

	handleConnected();
}

void NetworkManager::handleConnected() {
	m_connected = true;
	emit statusChanged("Connected", true);
	sendMeta();
}

void NetworkManager::handleDisconnected() {
	m_connected = false;
	m_metaSeen = false;
	m_syncPending = false;
	m_peerAutoMerge = false;
	dialTimer->stop();
	idleTimer->stop();
	emit statusChanged("Idle", false);

	if (socket) {
		socket->disconnect();
		socket->deleteLater();
		socket = nullptr;
	}
}

void NetworkManager::handleDialTimeout() {
	if (m_metaSeen) return;
	if (socket && socket->state() != QAbstractSocket::UnconnectedState) {
		socket->abort();
	}
	handleDisconnected();
	emit statusChanged("No Peer", false);
}

void NetworkManager::handleSocketError(QAbstractSocket::SocketError) {
	if (socket) {
		emit statusChanged(socket->errorString(), false);
		handleDisconnected();
	}
}

void NetworkManager::handleReadyRead() {
	if (!socket) return;

	while (socket->canReadLine()) {
		QByteArray line = socket->readLine().trimmed();
		if (!line.isEmpty()) {
			QByteArray decodedBytes = QByteArray::fromBase64(line);
			QJsonDocument doc = QJsonDocument::fromJson(decodedBytes);

			if (doc.isObject()) {
				QJsonObject json = doc.object();
				QString receivedKey = json["key"].toString();

				QString localKey = ConfigManager::loadNote().passKey;
				if (receivedKey != localKey) {
					qDebug() << "Key Fail";
					emit statusChanged("Pass Key Incorrect", false);
					socket->abort();
					return;
				}

				const QString type = json.contains("type") ? json["type"].toString() : "data";
				if (type == "meta") {
					m_peerLastModified = json["date"].toVariant().toLongLong();
					m_peerAutoMerge = json["auto"].toBool();
					emit statusChanged("Connected", true);
					m_metaSeen = true;
					dialTimer->stop();
					if (m_syncPending) { m_syncPending = false; emit linkReady(); }
					if (autoMergeReady()) emit autoSyncDue();
				} else {
					m_peerLastModified = json["date"].toVariant().toLongLong();
					m_peerAutoMerge = json["auto"].toBool();
					QString cleanMessage = json["data"].toString();
					emit contentReceived(cleanMessage);
					emit statusChanged("Received", true);
					m_metaSeen = true;
					dialTimer->stop();
					if (m_syncPending) { m_syncPending = false; emit linkReady(); }
					noteActivity();
				}
			}
		}
	}
}

void NetworkManager::sendNoteContent(const QString &content, const QString &passKey, qint64 lastModified) {
	if (!m_connected || !socket) return;

	QJsonObject json;
	json["type"] = "data";
	json["key"] = passKey;
	json["date"] = lastModified;
	json["auto"] = m_autoMerge;
	json["data"] = content;

	QByteArray encodedPayload = QJsonDocument(json).toJson(QJsonDocument::Compact).toBase64();
	encodedPayload.append('\n');
	if (socket->write(encodedPayload) != -1) {
		socket->flush();
		m_peerLastModified = lastModified;
		emit statusChanged("Sent", true);
	}
}

bool NetworkManager::isConnected() const {
	return m_connected;
}

qint64 NetworkManager::peerLastModified() const {
	return m_peerLastModified;
}

void NetworkManager::sendMeta() {
	if (!socket) return;
	NoteData local = ConfigManager::loadNote();
	QJsonObject json;
	json["type"] = "meta";
	json["key"] = local.passKey;
	json["date"] = local.lastModified;
	json["auto"] = m_autoMerge;
	QByteArray payload = QJsonDocument(json).toJson(QJsonDocument::Compact).toBase64();
	payload.append('\n');
	if (socket->write(payload) != -1) {
		socket->flush();
	}
}

void NetworkManager::disconnectFromPeer() {
	if (socket && socket->state() != QAbstractSocket::UnconnectedState) {
		socket->abort(); 
	} else {
		handleDisconnected();
	}
}

bool NetworkManager::autoMergeReady() const {
	return m_connected && m_autoMerge && m_peerAutoMerge;
}

void NetworkManager::setAutoMerge(bool on) {
	if (m_autoMerge == on) return;
	m_autoMerge = on;
	if (m_connected) sendMeta();
	if (autoMergeReady()) emit autoSyncDue();
	else idleTimer->stop();
}

void NetworkManager::noteActivity() {
	if (!autoMergeReady()) { idleTimer->stop(); return; }
	idleTimer->start();
}

void NetworkManager::handleIdleTimeout() {
	if (autoMergeReady()) emit autoSyncDue();
}