/*
 * Copyright (C) 2026 UBports Foundation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "../../src/RcloneDaemon.h"
#include <utils/RcloneTestUtils.h>

#include <gtest/gtest.h>
#include <QCoreApplication>
#include <QHostAddress>
#include <QSignalSpy>
#include <QTcpSocket>

using namespace std;

namespace
{

constexpr int SIGNAL_WAIT_TIME = 30000;

// QSignalSpy::wait() returns false when the signal has already been emitted,
// check the count first.
bool wait_for(QSignalSpy& spy, int timeout = SIGNAL_WAIT_TIME)
{
    return spy.count() > 0 || spy.wait(timeout);
}

bool port_accepts_connections(int port)
{
    QTcpSocket sock;
    sock.connectToHost(QHostAddress::LocalHost, port);
    return sock.waitForConnected(1000);
}

}  // namespace

class RcloneDaemonTests : public ::testing::Test
{
};

TEST_F(RcloneDaemonTests, starts_and_listens)
{
    RcloneDaemon daemon;
    QSignalSpy listening(&daemon, &RcloneDaemon::listening);
    QSignalSpy failed(&daemon, &RcloneDaemon::failed);

    QUrl const url = daemon.base_url();
    EXPECT_EQ("http", url.scheme());
    EXPECT_EQ("127.0.0.1", url.host());
    EXPECT_NE(-1, url.port());
    EXPECT_EQ("/", url.path());
    EXPECT_EQ("storage-provider-rclone", daemon.user());
    EXPECT_FALSE(daemon.password().isEmpty());

    ASSERT_TRUE(wait_for(listening));
    EXPECT_EQ(0, failed.count());
    
    EXPECT_TRUE(daemon.state() == RcloneDaemon::State::Starting);
    daemon.set_ready();
    EXPECT_TRUE(daemon.state() == RcloneDaemon::State::Ready);
}

TEST_F(RcloneDaemonTests, independent_daemons)
{
    RcloneDaemon first;
    QSignalSpy first_listening(&first, &RcloneDaemon::listening);
    // Wait until the first daemon holds its port, otherwise the kernel is free
    // to hand the same one to the second daemon.
    ASSERT_TRUE(wait_for(first_listening));
    first.set_ready();

    RcloneDaemon second;
    QSignalSpy second_listening(&second, &RcloneDaemon::listening);
    ASSERT_TRUE(wait_for(second_listening));
    second.set_ready();

    EXPECT_NE(first.base_url().port(), second.base_url().port());
    EXPECT_NE(first.password(), second.password());
}

TEST_F(RcloneDaemonTests, state_transitions_are_one_way)
{
    RcloneDaemon daemon;
    QSignalSpy ready(&daemon, &RcloneDaemon::ready);
    QSignalSpy failed(&daemon, &RcloneDaemon::failed);

    daemon.set_ready();
    daemon.set_ready();
    EXPECT_TRUE(daemon.state() == RcloneDaemon::State::Ready);
    EXPECT_EQ(1, ready.count());

    daemon.set_failed(QStringLiteral("first reason"));
    daemon.set_failed(QStringLiteral("second reason"));
    daemon.set_ready();

    EXPECT_TRUE(daemon.state() == RcloneDaemon::State::Failed);
    EXPECT_EQ("first reason", daemon.error_string());
    ASSERT_EQ(1, failed.count());
    EXPECT_EQ("first reason", failed.at(0).at(0).toString());
    EXPECT_EQ(1, ready.count());
}

TEST_F(RcloneDaemonTests, binary_not_on_path_fails)
{
    QByteArray const path = qgetenv("PATH");
    qputenv("PATH", "/nonexistent");
    RcloneDaemon daemon;
    qputenv("PATH", path);

    QSignalSpy failed(&daemon, &RcloneDaemon::failed);
    QSignalSpy listening(&daemon, &RcloneDaemon::listening);

    ASSERT_TRUE(wait_for(failed));
    EXPECT_TRUE(daemon.state() == RcloneDaemon::State::Failed);
    EXPECT_TRUE(daemon.error_string().contains("failed to start"))
        << daemon.error_string().toStdString();
    EXPECT_EQ(0, listening.count());
}

TEST_F(RcloneDaemonTests, destructor_stops_the_child)
{
    int port = 0;
    {
        RcloneDaemon daemon;
        QSignalSpy listening(&daemon, &RcloneDaemon::listening);
        ASSERT_TRUE(wait_for(listening));
        daemon.set_ready();
        port = daemon.base_url().port();
        ASSERT_TRUE(port_accepts_connections(port));
    }
    EXPECT_FALSE(port_accepts_connections(port));
}

TEST_F(RcloneDaemonTests, destructor_stops_a_starting_child)
{
    int port = 0;
    {
        RcloneDaemon daemon;
        QSignalSpy listening(&daemon, &RcloneDaemon::listening);
        ASSERT_TRUE(wait_for(listening));
        port = daemon.base_url().port();
        ASSERT_TRUE(daemon.state() == RcloneDaemon::State::Starting);
    }
    EXPECT_FALSE(port_accepts_connections(port));
}

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
