#include <QtTest/QtTest>
#include "actors/actor.h"
#include "actors/timeractor.h"
#include "actors/sensoractor.h"
#include "actors/scriptactor.h"
#include "sensors/sensor.h"

class TestActor : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase()
    {
        // Setup for all tests
    }

    void testActorCreation()
    {
        Actor actor;
        
        // Actor should be active by default
        QVERIFY(actor.isActive());
        
        // Actor should not be exhausted by default
        QVERIFY(!actor.isExausted());
    }

    void testActorActivation()
    {
        Actor actor;
        
        // Test makeActive/makeInactive
        actor.makeActive();
        QVERIFY(actor.isActive());
        
        actor.makeInactive();
        QVERIFY(!actor.isActive());
    }

    void testActorSetActive()
    {
        Actor actor;
        
        // Test setActive
        actor.setActive(1);
        QVERIFY(actor.isActive());
        
        actor.setActive(0);
        QVERIFY(!actor.isActive());
    }

    void testActorTriggerValue()
    {
        Actor actor;
        
        // Default trigger value should be 0
        QVERIFY(actor.getTriggerValue() == 0);
        
        // Set trigger value
        actor.setTriggerValue(1);
        QVERIFY(actor.getTriggerValue() == 1);
        
        actor.setTriggerValue(255);
        QVERIFY(actor.getTriggerValue() == 255);
    }

    void testActorActionName()
    {
        Actor actor;
        
        // Default trigger value is 0, so action name should be "off"
        QVERIFY(actor.actionName() == "off");
        
        // Set trigger value to 1
        actor.setTriggerValue(1);
        QVERIFY(actor.actionName() == "on");
        
        // Set trigger value to something else
        actor.setTriggerValue(5);
        QVERIFY(actor.actionName() == "value to 5");
    }

    void testActorJsonSerialization()
    {
        Actor actor;
        actor.setTriggerValue(1);
        actor.makeInactive();
        
        QJsonObject json;
        actor.store(json);
        
        // Verify JSON contents
        QVERIFY(json.contains("Active"));
        QVERIFY(json.contains("Exausted"));
        QVERIFY(json.contains("TriggerValue"));
        
        QVERIFY(json["Active"].toBool() == false);
        QVERIFY(json["Exausted"].toBool() == false);
        QVERIFY(json["TriggerValue"].toInt() == 1);
    }

    void testActorJsonDeserialization()
    {
        QJsonObject json;
        json["Active"] = false;
        json["Exausted"] = true;
        json["TriggerValue"] = 5;
        
        Actor actor;
        actor.load(json);
        
        QVERIFY(actor.isActive() == false);
        QVERIFY(actor.isExausted() == true);
        QVERIFY(actor.getTriggerValue() == 5);
    }

    void testActorCreateActor()
    {
        // Test creating different actor types
        std::shared_ptr<Actor> alarmActor = Actor::createActor("Alarm");
        QVERIFY(alarmActor != nullptr);
        
        std::shared_ptr<Actor> sensorActor = Actor::createActor("Sensor");
        QVERIFY(sensorActor != nullptr);
        
        std::shared_ptr<Actor> timerActor = Actor::createActor("Timer");
        QVERIFY(timerActor != nullptr);
        
        std::shared_ptr<Actor> regulatorActor = Actor::createActor("Regulator");
        QVERIFY(regulatorActor != nullptr);
        
        std::shared_ptr<Actor> polynomalActor = Actor::createActor("Polynomal");
        QVERIFY(polynomalActor != nullptr);
        
        std::shared_ptr<Actor> scriptActor = Actor::createActor("Script");
        QVERIFY(scriptActor != nullptr);
        
        std::shared_ptr<Actor> genericActor = Actor::createActor("Actor");
        QVERIFY(genericActor != nullptr);
        
        // Test unknown type returns nullptr
        std::shared_ptr<Actor> unknownActor = Actor::createActor("UnknownType");
        QVERIFY(unknownActor == nullptr);
    }

    void testActorLoadActor()
    {
        QJsonObject json;
        json["Type"] = "Timer";
        json["Active"] = true;
        json["Exausted"] = false;
        json["TriggerValue"] = 1;
        json["Timeout"] = 5000;
        
        std::shared_ptr<Actor> actor = Actor::loadActor(json);
        QVERIFY(actor != nullptr);
        
        // Verify the actor was loaded with correct values
        QVERIFY(actor->isActive());
        QVERIFY(!actor->isExausted());
        QVERIFY(actor->getTriggerValue() == 1);
    }

    void testTimerActorCreation()
    {
        TimerActor actor(60);
        
        // Default timeout should be 60 seconds
        QVERIFY(actor.getTimeout() == 60);
    }

    void testTimerActorSetTimeout()
    {
        TimerActor actor(60);
        
        actor.setTimeout(120);
        QVERIFY(actor.getTimeout() == 120);
        
        actor.setTimeout(5);
        QVERIFY(actor.getTimeout() == 5);
    }

    void testTimerActorJsonSerialization()
    {
        TimerActor actor(120);
        
        QJsonObject json;
        actor.store(json);
        
        // Verify JSON contents
        QVERIFY(json.contains("Type"));
        QVERIFY(json.contains("Timeout"));
        
        QVERIFY(json["Type"].toString() == "Timer");
        QVERIFY(json["Timeout"].toInt() == 120000); // Converted to milliseconds
    }

    void testTimerActorJsonDeserialization()
    {
        QJsonObject json;
        json["Type"] = "Timer";
        json["Timeout"] = 30000; // 30 seconds in milliseconds
        json["Active"] = true;
        json["Exausted"] = false;
        json["TriggerValue"] = 1;
        
        TimerActor actor;
        actor.load(json, false);
        
        // Timeout should be 30 seconds
        QVERIFY(actor.getTimeout() == 30);
    }

    void testTimerActorGetName()
    {
        TimerActor actor(60);
        
        QString name = actor.getName();
        QVERIFY(name.contains("60"));
    }

    void testSensorActorCreation()
    {
        Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
        SensorActor actor(sensor);
        
        // Verify sensor was set correctly
        QVERIFY(actor.getSensor().type == Sensor::TYPE_TEMPERATURE);
        QVERIFY(actor.getSensor().id == 1);
    }

    void testSensorActorDefaultCreation()
    {
        SensorActor actor;
        
        // Default sensor should be dummy
        QVERIFY(actor.getSensor().type == Sensor::TYPE_DUMMY);
    }

    void testSensorActorSetSensor()
    {
        SensorActor actor;
        
        Sensor newSensor(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity_sensor");
        actor.setSensor(newSensor);
        
        QVERIFY(actor.getSensor().type == Sensor::TYPE_HUMIDITY);
        QVERIFY(actor.getSensor().id == 2);
    }

    void testSensorActorSetThreshold()
    {
        SensorActor actor;
        
        actor.setThreshold(25.0);
        QVERIFY(actor.getThreshold() == 25.0);
    }

    void testSensorActorSetSlope()
    {
        SensorActor actor;
        
        actor.setSloap(SensorActor::SLOPE_UP);
        QVERIFY(actor.getSloap() == SensorActor::SLOPE_UP);
        
        actor.setSloap(SensorActor::SLOPE_DOWN);
        QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN);
        
        actor.setSloap(SensorActor::SLOPE_BOTH);
        QVERIFY(actor.getSloap() == SensorActor::SLOPE_BOTH);
    }

    void testSensorActorJsonSerialization()
    {
        Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
        SensorActor actor(sensor);
        actor.setThreshold(30.0);
        actor.setSloap(SensorActor::SLOPE_UP);
        
        QJsonObject json;
        actor.store(json);
        
        // Verify JSON contents
        QVERIFY(json.contains("Type"));
        QVERIFY(json.contains("Threshold"));
        QVERIFY(json.contains("Sloap"));
        QVERIFY(json.contains("SensorType"));
        
        QVERIFY(json["Type"].toString() == "Sensor");
        QVERIFY(json["Threshold"].toDouble() == 30.0);
        QVERIFY(json["Sloap"].toInt() == SensorActor::SLOPE_UP);
    }

    void testSensorActorJsonDeserialization()
    {
        QJsonObject json;
        json["Type"] = "Sensor";
        json["Threshold"] = 25.5;
        json["Sloap"] = SensorActor::SLOPE_DOWN;
        json["SensorType"] = Sensor::TYPE_HUMIDITY;
        json["SensorId"] = 3;
        json["SensorField"] = 65.0;
        json["SensorName"] = "humidity";
        json["Active"] = true;
        json["Exausted"] = false;
        json["TriggerValue"] = 1;
        
        SensorActor actor;
        actor.load(json, false);
        
        QVERIFY(actor.getThreshold() == 25.5);
        QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN);
    }

    void testSensorActorGetName()
    {
        Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
        SensorActor actor(sensor);
        actor.setThreshold(30.0);
        
        QString name = actor.getName();
        QVERIFY(name.contains("temp_sensor"));
        QVERIFY(name.contains("30"));
    }

    // ScriptActor tests
    void testScriptActorCreation()
    {
        ScriptActor actor;
        
        // Should start with empty sensors
        QVERIFY(actor.getSensors().isEmpty());
        
        // Should not watch parent item by default
        QVERIFY(!actor.getWatchParentItem());
        
        // Script path and code should be empty
        QVERIFY(actor.getScriptPath().isEmpty());
        QVERIFY(actor.getScriptCode().isEmpty());
    }

    void testScriptActorSensors()
    {
        ScriptActor actor;
        
        // Add sensors
        Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp1");
        Sensor sensor2(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity");
        
        actor.addSensor(sensor1);
        actor.addSensor(sensor2);
        
        QVERIFY(actor.getSensors().size() == 2);
        
        // Adding duplicate (same type+id) should not add again
        Sensor sensor1Duplicate(Sensor::TYPE_TEMPERATURE, 1, 30.0, "temp1_dup");
        actor.addSensor(sensor1Duplicate);
        QVERIFY(actor.getSensors().size() == 2);
        
        // Remove sensor
        actor.removeSensor(sensor1);
        QVERIFY(actor.getSensors().size() == 1);
        QVERIFY(actor.getSensors()[0].type == Sensor::TYPE_HUMIDITY);
        
        // Clear sensors
        actor.clearSensors();
        QVERIFY(actor.getSensors().isEmpty());
    }

    void testScriptActorSensorMatching()
    {
        ScriptActor actor;
        
        // Add a temperature sensor with id 5
        Sensor watchedSensor(Sensor::TYPE_TEMPERATURE, 5, 20.0, "watched_temp");
        actor.addSensor(watchedSensor);
        
        // A different temperature sensor (id 10) should NOT match
        Sensor otherTempSensor(Sensor::TYPE_TEMPERATURE, 10, 25.0, "other_temp");
        QVERIFY(!(otherTempSensor.type == watchedSensor.type && otherTempSensor.id == watchedSensor.id));
        
        // A humidity sensor with same id (5) should NOT match
        Sensor sameIdHumidity(Sensor::TYPE_HUMIDITY, 5, 50.0, "humidity_same_id");
        QVERIFY(!(sameIdHumidity.type == watchedSensor.type && sameIdHumidity.id == watchedSensor.id));
    }

    void testScriptActorWatchParentItem()
    {
        ScriptActor actor;
        
        QVERIFY(!actor.getWatchParentItem());
        
        actor.setWatchParentItem(true);
        QVERIFY(actor.getWatchParentItem());
        
        actor.setWatchParentItem(false);
        QVERIFY(!actor.getWatchParentItem());
    }

    void testScriptActorScriptPath()
    {
        ScriptActor actor;
        
        QVERIFY(actor.getScriptPath().isEmpty());
        
        actor.setScriptPath("/path/to/script.py");
        QVERIFY(actor.getScriptPath() == "/path/to/script.py");
    }

    void testScriptActorScriptCode()
    {
        ScriptActor actor;
        
        QVERIFY(actor.getScriptCode().isEmpty());
        
        actor.setScriptCode("return 42");
        QVERIFY(actor.getScriptCode() == "return 42");
    }

    void testScriptActorJsonSerialization()
    {
        ScriptActor actor;
        actor.setWatchParentItem(true);
        actor.setScriptPath("/path/to/script.py");
        actor.setScriptCode("return 1");
        
        Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp1");
        Sensor sensor2(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity");
        actor.addSensor(sensor1);
        actor.addSensor(sensor2);
        
        QJsonObject json;
        actor.store(json);
        
        // Verify JSON contents
        QVERIFY(json["Type"].toString() == "Script");
        QVERIFY(json["WatchParentItem"].toBool() == true);
        QVERIFY(json["ScriptPath"].toString() == "/path/to/script.py");
        QVERIFY(json["ScriptCode"].toString() == "return 1");
        
        QVERIFY(json.contains("Sensors"));
        QJsonArray sensorsArray = json["Sensors"].toArray();
        QVERIFY(sensorsArray.size() == 2);
        
        // Check first sensor
        QVERIFY(sensorsArray[0].toObject()["Type"].toInt() == Sensor::TYPE_TEMPERATURE);
        QVERIFY(sensorsArray[0].toObject()["Id"].toInteger() == 1);
    }

    void testScriptActorJsonDeserialization()
    {
        QJsonObject json;
        json["Type"] = "Script";
        json["WatchParentItem"] = true;
        json["ScriptPath"] = "/custom/script.py";
        json["ScriptCode"] = "return 99";
        json["Active"] = true;
        json["Exausted"] = false;
        json["TriggerValue"] = 5;
        
        QJsonArray sensorsArray;
        QJsonObject sensor1;
        sensor1["Type"] = Sensor::TYPE_TEMPERATURE;
        sensor1["Id"] = 10;
        sensor1["Name"] = "loaded_temp";
        sensorsArray.append(sensor1);
        json["Sensors"] = sensorsArray;
        
        ScriptActor actor;
        actor.load(json, false);
        
        QVERIFY(actor.getWatchParentItem() == true);
        QVERIFY(actor.getScriptPath() == "/custom/script.py");
        QVERIFY(actor.getScriptCode() == "return 99");
        QVERIFY(actor.getTriggerValue() == 5);
        QVERIFY(actor.isActive() == true);
        
        QVERIFY(actor.getSensors().size() == 1);
        QVERIFY(actor.getSensors()[0].type == Sensor::TYPE_TEMPERATURE);
        QVERIFY(actor.getSensors()[0].id == 10);
        QVERIFY(actor.getSensors()[0].name == "loaded_temp");
    }

    void testScriptActorLoadActor()
    {
        QJsonObject json;
        json["Type"] = "Script";
        json["WatchParentItem"] = false;
        json["ScriptCode"] = "return 42";
        json["Active"] = true;
        json["Exausted"] = false;
        json["TriggerValue"] = 1;
        
        std::shared_ptr<Actor> actor = Actor::loadActor(json);
        QVERIFY(actor != nullptr);
        
        // Verify the actor was loaded with correct values
        QVERIFY(actor->isActive());
        QVERIFY(!actor->isExausted());
        QVERIFY(actor->getTriggerValue() == 1);
    }

    void testScriptActorGetName()
    {
        ScriptActor actor;
        
        // Without sensors, name should just be "Script"
        QString name = actor.getName();
        QVERIFY(name == "Script");
        
        // With sensors
        Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
        actor.addSensor(sensor1);
        
        name = actor.getName();
        QVERIFY(name.contains("Script"));
        QVERIFY(name.contains("temp_sensor"));
        
        // With parent item watching
        actor.setWatchParentItem(true);
        name = actor.getName();
        QVERIFY(name.contains("parent item"));
    }

    void testScriptActorTriggerSync()
    {
        ScriptActor actor;
        actor.setTriggerValue(5);
        
        // triggerSync should not crash
        // It emits sigItemUpdate which will be handled by parent item
        actor.triggerSync();
        
        // Verify trigger value is unchanged
        QVERIFY(actor.getTriggerValue() == 5);
    }

    void testScriptActorRunScript()
    {
        ScriptActor actor;
        actor.setTriggerValue(0);
        
        // Set a simple script that returns 42
        actor.setScriptCode("return 42");
        
        // Run the script
        QVariant result = actor.runScriptNow();
        
        // Verify the result is 42
        QVERIFY(result.isValid());
        QVERIFY(result.toInt() == 42);
        
        // Note: performValueAction emits sigItemUpdate but doesn't update triggerValue
        // triggerValue is set manually and used for manual activation
    }

    void testScriptActorRunScriptWithDicts()
    {
        ScriptActor actor;
        actor.setTriggerValue(0);
        
        // Set a script that uses the available dicts
        actor.setScriptCode("return len(items) + len(sensors)");
        
        // Run the script - should not crash
        QVariant result = actor.runScriptNow();
        
        // Result should be valid (number of items + sensors)
        QVERIFY(result.isValid());
    }

    void testScriptActorRunScriptMultipleTimes()
    {
        ScriptActor actor;
        actor.setTriggerValue(0);
        
        // Set a script that increments
        actor.setScriptCode("return 1");
        
        // Run multiple times - should not crash
        for(int i = 0; i < 3; i++)
        {
            QVariant result = actor.runScriptNow();
            QVERIFY(result.isValid());
            QVERIFY(result.toInt() == 1);
        }
    }

    void cleanupTestCase()
    {
        // Cleanup after all tests
    }
};

QTEST_APPLESS_MAIN(TestActor)

#include "test_actor.moc"