// Python.h must be included before any Qt headers that use 'slots' keyword
extern "C" {
#include <Python.h>
}

#include "scriptactor.h"
#include "items/itemstore.h"
#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QTextStream>

// Python stdout/stderr redirection
static PyObject* stdout_write(PyObject* self, PyObject* args)
{
	(void)self;
	char* text = nullptr;
	if(PyArg_ParseTuple(args, "s", &text))
	{
		qDebug().noquote() << "[Script stdout]" << QString::fromUtf8(text).trimmed();
	}
	Py_RETURN_NONE;
}

static PyObject* stderr_write(PyObject* self, PyObject* args)
{
	(void)self;
	char* text = nullptr;
	if(PyArg_ParseTuple(args, "s", &text))
	{
		qWarning().noquote() << "[Script stderr]" << QString::fromUtf8(text).trimmed();
	}
	Py_RETURN_NONE;
}

static PyMethodDef stdout_methods[] = {
	{"write", stdout_write, METH_VARARGS, "stdout.write"},
	{nullptr, nullptr, 0, nullptr}
};

static PyMethodDef stderr_methods[] = {
	{"write", stderr_write, METH_VARARGS, "stderr.write"},
	{nullptr, nullptr, 0, nullptr}
};

static PyModuleDef stdout_module = {
	PyModuleDef_HEAD_INIT, "stdout", nullptr, -1, stdout_methods
};

static PyModuleDef stderr_module = {
	PyModuleDef_HEAD_INIT, "stderr", nullptr, -1, stderr_methods
};

static PyObject* init_stdout_module()
{
	return PyModule_Create(&stdout_module);
}

static PyObject* init_stderr_module()
{
	return PyModule_Create(&stderr_module);
}

// Track initialization state
static bool pythonInitialized = false;
static bool modulesRegistered = false;

ScriptActor::ScriptActor(QObject* parent): Actor(parent)
{
}

void ScriptActor::addSensor(const Sensor& sensor)
{
	// Check if sensor with same type+id already exists
	for(const auto& s : sensors_)
	{
		if(s.type == sensor.type && s.id == sensor.id)
			return;
	}
	sensors_.append(sensor);
}

void ScriptActor::removeSensor(const Sensor& sensor)
{
	for(int i = 0; i < sensors_.size(); ++i)
	{
		if(sensors_[i].type == sensor.type && sensors_[i].id == sensor.id)
		{
			sensors_.removeAt(i);
			return;
		}
	}
}

void ScriptActor::clearSensors()
{
	sensors_.clear();
}

QVector<Sensor> ScriptActor::getSensors() const
{
	return sensors_;
}

void ScriptActor::setWatchParentItem(bool watch)
{
	watchParentItem_ = watch;
}

bool ScriptActor::getWatchParentItem() const
{
	return watchParentItem_;
}

void ScriptActor::setScriptPath(const QString& path)
{
	scriptPath_ = path;
}

QString ScriptActor::getScriptPath() const
{
	return scriptPath_;
}

void ScriptActor::setScriptCode(const QString& code)
{
	scriptCode_ = code;
}

QString ScriptActor::getScriptCode() const
{
	return scriptCode_;
}

void ScriptActor::sensorEvent(Sensor sensor, sensor_update_type_t type)
{
	(void)type;
	// Match by type AND id
	for(const auto& watchedSensor : sensors_)
	{
		if(watchedSensor.type == sensor.type && watchedSensor.id == sensor.id)
		{
			QVariantMap triggerSource;
			triggerSource["type"] = "sensor";
			triggerSource["name"] = sensor.name;
			triggerSource["sensor_type"] = static_cast<int>(sensor.type);
			triggerSource["sensor_id"] = QString::number(sensor.id);
			triggerSource["value"] = sensor.field;
			runScriptInternal(triggerSource);
			return;
		}
	}
}

void ScriptActor::onItemUpdated(ItemUpdateRequest update)
{
	if(watchParentItem_)
	{
		QVariantMap triggerSource;
		triggerSource["type"] = "item";
		triggerSource["name"] = update.payload.getName();
		triggerSource["value"] = update.payload.getValue();
		runScriptInternal(triggerSource);
	}
	(void)update;
}

QVariant ScriptActor::runScriptNow()
{
	QVariantMap triggerSource;
	triggerSource["type"] = "manual";
	triggerSource["name"] = "Run Now button";
	return runScriptInternal(triggerSource);
}

void ScriptActor::triggerSync()
{
	// Emit sigItemUpdate to trigger the sync mechanism
	// This will cause the item to be updated and synced to the primary instance
	ItemUpdateRequest request;
	request.type = ITEM_UPDATE_ACTOR;
	request.payload = ItemData(QRandomGenerator::global()->generate(), "Item", triggerValue);
	request.changes.value = true;
	sigItemUpdate(request);
}

QVariant ScriptActor::runScriptInternal(const QVariantMap& triggerSource)
{
	if(scriptCode_.isEmpty() && scriptPath_.isEmpty())
		return QVariant();

	// Initialize Python interpreter - must register modules BEFORE Py_Initialize()
	if(!pythonInitialized)
	{
		// Register custom stdout/stderr modules BEFORE initialization
		PyImport_AppendInittab("stdout", &init_stdout_module);
		PyImport_AppendInittab("stderr", &init_stderr_module);
		modulesRegistered = true;

		Py_Initialize();
		pythonInitialized = true;
		
		// Check for initialization errors
		if(PyErr_Occurred())
		{
			PyErr_Print();
			qWarning() << "Python initialization failed";
			return QVariant();
		}
	}

	if(!Py_IsInitialized())
		return QVariant();

	// Build items dict
	PyObject* itemsDict = PyDict_New();
	if(itemsDict)
	{
		for(const auto& item : *globalItems.getItems())
		{
			QString key = item->getName();
			if(key.isEmpty())
				key = "Item_" + QString::number(item->id());
			PyDict_SetItemString(itemsDict, key.toStdString().c_str(), PyLong_FromUnsignedLong(item->getValue()));
		}
	}

	// Build sensors dict
	PyObject* sensorsDict = PyDict_New();
	if(sensorsDict)
	{
		for(const auto& sensor : *globalSensors.getSensors())
		{
			QString key = sensor.name;
			if(key.isEmpty())
				key = "Sensor_" + QString::number(sensor.id);
			PyDict_SetItemString(sensorsDict, key.toStdString().c_str(), PyFloat_FromDouble(sensor.field));
		}
	}

	// Build trigger_source dict
	PyObject* triggerSourceDict = PyDict_New();
	if(triggerSourceDict)
	{
		for(auto it = triggerSource.constBegin(); it != triggerSource.constEnd(); ++it)
		{
			QVariant value = it.value();
			PyObject* pyValue = nullptr;
			if(value.typeId() == QMetaType::QString)
				pyValue = PyUnicode_FromString(value.toString().toStdString().c_str());
			else if(value.typeId() == QMetaType::Int || value.typeId() == QMetaType::LongLong)
				pyValue = PyLong_FromLong(value.toLongLong());
			else if(value.typeId() == QMetaType::UInt)
				pyValue = PyLong_FromUnsignedLong(value.toUInt());
			else if(value.typeId() == QMetaType::Double)
				pyValue = PyFloat_FromDouble(value.toDouble());
			else if(value.canConvert<QString>())
				pyValue = PyUnicode_FromString(value.toString().toStdString().c_str());
			
			if(pyValue)
				PyDict_SetItemString(triggerSourceDict, it.key().toStdString().c_str(), pyValue);
		}
	}

	// Build controlled_item dict (this actor's parent item)
	PyObject* controlledItemDict = PyDict_New();
	if(controlledItemDict)
	{
		PyDict_SetItemString(controlledItemDict, "name", PyUnicode_FromString(getName().toStdString().c_str()));
		PyDict_SetItemString(controlledItemDict, "value", PyLong_FromUnsignedLong(getValue()));
		PyDict_SetItemString(controlledItemDict, "id", PyLong_FromUnsignedLong(id()));
	}

	PyObject* result = nullptr;

	// Create stdout/stderr objects - use the module directly (it has a write method)
	PyObject* stdoutModule = PyImport_ImportModule("stdout");
	PyObject* stdoutObj = stdoutModule; // Use module directly - it has write() method
	PyObject* stderrModule = PyImport_ImportModule("stderr");
	PyObject* stderrObj = stderrModule; // Use module directly - it has write() method

	if(!scriptCode_.isEmpty())
	{
		// Wrap script in a function so return statements work
		// Normalize tabs to spaces and indent each line by 4 spaces
		QString normalizedCode = scriptCode_;
		normalizedCode.replace("\t", "    "); // Convert tabs to 4 spaces
		
		QString indentedCode;
		for(const QString& line : normalizedCode.split('\n'))
		{
			indentedCode += "    " + line.trimmed() + "\n";
		}
		
		QString wrappedCode = QString(R"(
def _script_func(items, sensors, trigger_source, controlled_item):
%1    return None
)")
			.arg(indentedCode);
		


		PyObject* globals = PyDict_New();
		PyObject* locals = PyDict_New();
		if(globals && locals)
		{
			PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
			PyDict_SetItemString(locals, "items", itemsDict);
			PyDict_SetItemString(locals, "sensors", sensorsDict);
			PyDict_SetItemString(locals, "trigger_source", triggerSourceDict);
			PyDict_SetItemString(locals, "controlled_item", controlledItemDict);

			// Redirect stdout/stderr
			PyObject* sysModules = PyImport_GetModuleDict();
			PyDict_SetItemString(sysModules, "sys", PyDict_New()); // Ensure sys exists
			PyObject* sysModule = PyDict_GetItemString(sysModules, "sys");
			if(sysModule)
			{
				if(stdoutObj) PyDict_SetItemString(sysModule, "stdout", stdoutObj);
				if(stderrObj) PyDict_SetItemString(sysModule, "stderr", stderrObj);
			}

			// First, define the function
			PyObject* funcResult = PyRun_String(wrappedCode.toStdString().c_str(), Py_file_input, globals, locals);

			// Check for Python errors
			if(PyErr_Occurred())
			{
				PyErr_Print();
				qWarning() << "Python function definition error";
			}
			Py_XDECREF(funcResult);

			// Now call the function to get the result
			// Check both globals and locals for the function
			PyObject* func = PyDict_GetItemString(globals, "_script_func");
			if(!func)
			{
				func = PyDict_GetItemString(locals, "_script_func");
			}
			
			if(func && PyCallable_Check(func))
			{
				PyObject* args = PyTuple_Pack(4, itemsDict, sensorsDict, triggerSourceDict, controlledItemDict);
				if(args)
				{
					result = PyObject_Call(func, args, nullptr);
					Py_DECREF(args);
					
					// Check for errors after call
					if(PyErr_Occurred())
					{
						PyErr_Print();
						qWarning() << "Python function call error";
					}
				}
			}
			else
			{
				qWarning() << "Function _script_func not found or not callable";
			}

			Py_DECREF(globals);
			Py_DECREF(locals);
		}
	}
	else if(!scriptPath_.isEmpty())
	{
		// Execute script file
		QFile file(scriptPath_);
		if(file.open(QIODevice::ReadOnly))
		{
			QByteArray code = file.readAll();
			file.close();

			PyObject* globals = PyDict_New();
			PyObject* locals = PyDict_New();
			if(globals && locals)
			{
				PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
				PyDict_SetItemString(locals, "items", itemsDict);
				PyDict_SetItemString(locals, "sensors", sensorsDict);
				PyDict_SetItemString(locals, "trigger_source", triggerSourceDict);
				PyDict_SetItemString(locals, "controlled_item", controlledItemDict);

				// Redirect stdout/stderr
				PyObject* sysModules = PyImport_GetModuleDict();
				PyDict_SetItemString(sysModules, "sys", PyDict_New());
				PyObject* sysModule = PyDict_GetItemString(sysModules, "sys");
				if(sysModule)
				{
					if(stdoutObj) PyDict_SetItemString(sysModule, "stdout", stdoutObj);
					if(stderrObj) PyDict_SetItemString(sysModule, "stderr", stderrObj);
				}

				result = PyRun_String(code.toStdString().c_str(), Py_file_input, globals, locals);

				// Check for Python errors
				if(PyErr_Occurred())
				{
					PyErr_Print();
					qWarning() << "Python script execution error";
				}

				Py_DECREF(globals);
				Py_DECREF(locals);
			}
		}
		else
		{
			qWarning() << "ScriptActor: Could not open script file:" << scriptPath_;
		}
	}

	// Cleanup stdout/stderr objects
	Py_XDECREF(stdoutObj);
	Py_XDECREF(stderrObj);
	Py_XDECREF(stdoutModule);
	Py_XDECREF(stderrModule);

	// Extract return value
	QVariant returnValue;
	if(result)
	{
		if(result == Py_None)
		{
			// Script returned None - no action
		}
		else if(PyLong_Check(result))
		{
			long val = PyLong_AsLong(result);
			returnValue = QVariant(static_cast<uint8_t>(val));
			performValueAction(returnValue.toUInt());
		}
		else if(PyFloat_Check(result))
		{
			double val = PyFloat_AsDouble(result);
			returnValue = QVariant(static_cast<uint8_t>(val));
			performValueAction(returnValue.toUInt());
		}
		Py_DECREF(result);
	}

	Py_DECREF(itemsDict);
	Py_DECREF(sensorsDict);
	Py_DECREF(triggerSourceDict);
	Py_DECREF(controlledItemDict);

	return returnValue;
}

QString ScriptActor::getName() const
{
	if(name_.size() > 0) return name_;
	QStringList sensorNames;
	for(const auto& sensor : sensors_)
	{
		sensorNames.append(sensor.name);
	}
	QString result = "Script";
	if(!sensorNames.isEmpty())
		result += " on " + sensorNames.join(", ");
	if(watchParentItem_)
		result += " + parent item";
	return result;
}

void ScriptActor::store(QJsonObject& json)
{
	json["Type"] = "Script";
	Actor::store(json);
	json["WatchParentItem"] = watchParentItem_;
	json["ScriptPath"] = scriptPath_;
	json["ScriptCode"] = scriptCode_;

	QJsonArray sensorsArray;
	for(const auto& sensor : sensors_)
	{
		QJsonObject sensorObj;
		sensorObj["Type"] = static_cast<int>(sensor.type);
		sensorObj["Id"] = static_cast<qint64>(sensor.id);
		sensorObj["Name"] = sensor.name;
		sensorsArray.append(sensorObj);
	}
	json["Sensors"] = sensorsArray;
}

void ScriptActor::load(const QJsonObject& json, bool preserve)
{
	Actor::load(json, preserve);
	watchParentItem_ = json["WatchParentItem"].toBool(false);
	scriptPath_ = json["ScriptPath"].toString();
	scriptCode_ = json["ScriptCode"].toString();

	sensors_.clear();
	QJsonArray sensorsArray = json["Sensors"].toArray();
	for(const auto& val : sensorsArray)
	{
		QJsonObject sensorObj = val.toObject();
		Sensor sensor;
		sensor.type = static_cast<Sensor::sensor_type_t>(sensorObj["Type"].toInt(0));
		sensor.id = sensorObj["Id"].toInteger(0);
		sensor.name = sensorObj["Name"].toString();
		sensors_.append(sensor);
	}
}