#pragma once
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <string>
#include <string_view>
#include <vector>
#include <map>
#include <thread>
#include <atomic>
#include <random>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <optional>
#include <regex>
#include <array>

#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>

#include <httplib.h>  // build with CPPHTTPLIB_OPENSSL_SUPPORT
#include <sys/wait.h> // for WEXITSTATUS

#include "ConfigService.h"
#include "Security/TlsKeyUtil.h"

namespace snoop
{

    class DeviceControlService
    {
    public:
        struct Task
        {
            uint64_t id{};
            std::string type;
            nlohmann::json payload = nlohmann::json::object();
        };

        // Handler returns: {success, result_json_string, error_message}
        using HandlerResult = std::tuple<bool, std::string, std::string>;
        using TaskHandler = std::function<HandlerResult(const Task &)>;

        struct Handlers
        {
            // Fill any you implement. Unset => default “not implemented”.
            TaskHandler onStartStream;
            TaskHandler onStopStream;
            TaskHandler onStartRecording;
            TaskHandler onStopRecording;
            TaskHandler onUpdateConfig;
            TaskHandler onSetDeepSleep;
            TaskHandler onRenewCert;
        };

        struct Controls
        {
            std::function<void()> stopStreamNow;    // e.g., streamer.StopWhip()
            std::function<void()> stopRecordingNow; // e.g., writerService->StopRecordingNow()
        };

        DeviceControlService(std::shared_ptr<ConfigService> cfg, Handlers handlers, Controls controls)
            : m_cfg(std::move(cfg)), m_handlers(std::move(handlers)), m_controls(std::move(controls))
        {
            m_stop = false;
            m_thread = std::thread(&DeviceControlService::RunLoop, this);
        }

        ~DeviceControlService()
        {
            m_stop = true;
            if (m_thread.joinable())
            {
                m_thread.join();
            }
        }

        void ArmDeepSleep(long long startMs, long long stopMs)
        {
            // Record sleep-until, and if we’re already inside the window, act now.
            const auto now = NowMs();
            if (stopMs <= startMs)
            {
                spdlog::warn("ArmDeepSleep: stop<=start");
                return;
            }

            // If already inside window -> enter immediately
            if (now >= startMs && now < stopMs)
            {
                this->EnterDeepSleepUntil(stopMs);
            }
            else
            {
                // Otherwise, set a future "until" and the loop will enter at startMs.
                // We’ll keep both values:
                m_sleepStartMs = startMs;
                m_sleepUntilMs = stopMs;
            }
        }

        static std::optional<long long> ParseRfc3339UtcToMs(const std::string &s)
        {
            // Minimal, robust-ish parser for "YYYY-MM-DDTHH:MM:SS[.frac]Z"
            // For production, consider date::parse or a small RFC3339 lib.
            std::tm tm{};
            char z = 'Z';
            double frac = 0.0;
            int n = 0;
            // accept with optional fractional seconds
            if (sscanf(s.c_str(), "%d-%d-%dT%d:%d:%d%n", &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
                       &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n) != 6)
                       {
                            return std::nullopt;
                       }
            tm.tm_year -= 1900;
            tm.tm_mon -= 1;
            const char *p = s.c_str() + n;
            if (*p == '.')
            {
                // consume fractional
                ++p;
                while (isdigit(*p)) {
                    ++p; // ignore exact fraction
                }                   
            }
            if (*p != 'Z')
            {
                return std::nullopt;
            }     
            time_t tt = timegm(&tm);
            if (tt < 0)
            {
                return std::nullopt;
            }
            return (long long)tt * 1000LL;
        }

        static long long NowMs()
        {
            using namespace std::chrono;
            return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
        }

    private:
        std::shared_ptr<ConfigService> m_cfg;
        Handlers m_handlers;
        std::thread m_thread;
        std::atomic<bool> m_stop{false};

        std::atomic<long long> m_sleepUntilMs{0}; // epoch ms; 0 = not sleeping
        std::atomic<long long> m_sleepStartMs{0};

        Controls m_controls;

        struct Url
        {
            std::string scheme; // http/https
            std::string host;   // hostname or ip
            int port = 0;       // default if 0
        };

        std::unique_ptr<httplib::SSLClient> m_taskClient;
        std::string m_taskHost;
        int m_taskPort = 0;
        bool m_taskHttps = true;
        std::filesystem::path m_taskCa;
        std::filesystem::path m_taskCrt;

        static Url ParseBase(const std::string &base)
        {
            // very small parser: scheme://host[:port]
            std::regex re(R"(^\s*(https?)://([^/:]+)(?::(\d+))?\s*$)");
            std::smatch m;
            if (!std::regex_match(base, m, re))
            {
                throw std::runtime_error("Invalid base URL for DeviceControlService: " + base);
            }
            Url u;
            u.scheme = m[1].str();
            u.host = m[2].str();
            u.port = m[3].matched ? std::stoi(m[3].str()) : (u.scheme == "https" ? 443 : 80);
            return u;
        }

        static std::optional<long long> JsonTimeToMs(const nlohmann::json &j)
        {
            if (j.contains("start_ms") && j.contains("stop_ms"))
            {
                return j.at("stop_ms").get<long long>(); // caller will use start_ms separately
            }
            if (j.contains("start") && j.contains("stop") && j["start"].is_string() && j["stop"].is_string())
            {
                auto sMs = ParseRfc3339UtcToMs(j["start"].get<std::string>());
                auto eMs = ParseRfc3339UtcToMs(j["stop"].get<std::string>());
                if (sMs && eMs)
                {
                    return *eMs; // caller will use start separately
                }                    
            }
            if (j.contains("start") && j["start"].is_string() &&
                j["start"].get<std::string>() == "now" && j.contains("for_s"))
            {
                long long forS = j["for_s"].get<long long>();
                return NowMs() + forS * 1000LL;
            }
            return std::nullopt;
        }

        // dumps the client key from keyring to a temp file and returns its path

        // Create HTTPS client configured for mTLS (or HTTP if base is http)
        std::unique_ptr<httplib::SSLClient> MakeClient(const Url &u,
                                                       const std::filesystem::path &ca,
                                                       const std::filesystem::path &crt,
                                                       const std::filesystem::path &key)
        {
            if (u.scheme == "https")
            {
                auto cli = std::make_unique<httplib::SSLClient>(u.host.c_str(), u.port, crt.string().c_str(), key.string().c_str(), std::string());
                cli->enable_server_certificate_verification(false);
                cli->set_ca_cert_path(ca.string().c_str());
                cli->set_connection_timeout(10);
                cli->set_read_timeout(60);
                cli->set_write_timeout(60);

                if (!cli->is_valid())
                {
                    spdlog::warn("[mTLS debug] SSLClient is not valid right after creation.");
                }
                else
                {
                    spdlog::debug("[mTLS debug] SSLClient initialized OK for host={} port={}", u.host, u.port);
                }
                return cli;
            }
        }

        // simple jittered sleep
        void SleepWithJitterOnce(std::mt19937 &rng, int baseSec, int jitterSec)
        {
            if (baseSec < 0)
            {
                baseSec = 0;
            }
            if (jitterSec < 0)
            {
                jitterSec = 0;
            }
            std::uniform_int_distribution<int> dist(-jitterSec, +jitterSec);
            int delay = baseSec + dist(rng);
            if (delay < 0)
            {
                delay = 0;
            } 
            std::this_thread::sleep_for(std::chrono::seconds(delay));
        }

        // default fallback if handler is not provided
        static HandlerResult NotImplemented(const Task &t)
        {
            std::string msg = "Handler not implemented for type: " + (t.type.empty() ? "<empty>" : t.type);
            spdlog::warn("{}", msg);
            // Include echo of payload for debugging
            nlohmann::json res = {
                {"type", t.type},
                {"payload", t.payload}};
            return {false, res.dump(), msg};
        }

        TaskHandler ResolveHandler(std::string_view type) const
        {
            if (type == "start_stream")
                return m_handlers.onStartStream ? m_handlers.onStartStream : NotImplemented;
            if (type == "stop_stream")
                return m_handlers.onStopStream ? m_handlers.onStopStream : NotImplemented;
            if (type == "start_recording")
                return m_handlers.onStartRecording ? m_handlers.onStartRecording : NotImplemented;
            if (type == "stop_recording")
                return m_handlers.onStopRecording ? m_handlers.onStopRecording : NotImplemented;
            if (type == "update_config")
                return m_handlers.onUpdateConfig ? m_handlers.onUpdateConfig : NotImplemented;
            if (type == "set_deep_sleep")
                return m_handlers.onSetDeepSleep ? m_handlers.onSetDeepSleep : NotImplemented;
            if (type == "renew_cert")
                return m_handlers.onRenewCert ? m_handlers.onRenewCert : NotImplemented;
            return NotImplemented;
        }

        static std::vector<Task> ParseTasks(const std::string &body)
        {
            std::vector<Task> out;
            nlohmann::json j = nlohmann::json::parse(body);

            auto to_id = [](const nlohmann::json &v) -> uint64_t
            {
                if (v.is_number_unsigned())
                {
                    return v.get<uint64_t>();
                }
                if (v.is_number_integer())
                {
                    return static_cast<uint64_t>(v.get<long long>());
                }
                if (v.is_string())
                {
                    try
                    {
                        return static_cast<uint64_t>(std::stoull(v.get<std::string>()));
                    }
                    catch (...)
                    {
                    }
                }
                return 0ULL;
            };

            auto parse_one = [&](const nlohmann::json &x)
            {
                Task t;
                if (x.contains("id"))
                {
                    t.id = to_id(x["id"]);
                }
                if (x.contains("type") && x["type"].is_string())
                {
                    t.type = x["type"].get<std::string>();
                }
                // payload can be stringified JSON or object
                try
                {
                    if (x.contains("payload"))
                    {
                        const auto &raw = x.at("payload");
                        if (raw.is_string())
                        {
                            try
                            {
                                t.payload = nlohmann::json::parse(raw.get<std::string>());
                            }
                            catch (...)
                            {
                                t.payload = raw.get<std::string>();
                            }
                        }
                        else if (raw.is_object() || raw.is_array())
                        {
                            t.payload = raw;
                        }
                        else
                        {
                            t.payload = nlohmann::json::object();
                        }
                    }
                }
                catch (...)
                {
                    t.payload = nlohmann::json::object();
                }

                if (t.id == 0)
                {
                    spdlog::warn("Task without valid id: {}", x.dump());
                }
                if (t.type.empty())
                {
                    spdlog::warn("Task without type: {}", x.dump());
                }
                out.push_back(std::move(t));
            };

            if (j.is_array())
            {
                for (const auto &it : j)
                    parse_one(it);
            }
            else if (j.is_object())
            {
                // NEW: wrapper shape { hasTask: bool, task: { ... } }
                if (j.contains("task") && j["task"].is_object())
                {
                    // If server includes a 'status' gate and you only want runnable tasks, you can check here:
                    // if (j.value("hasTask", false) == true)
                    parse_one(j["task"]);
                }
                else
                {
                    // bare single task
                    parse_one(j);
                }
            }
            else
            {
                spdlog::warn("Unexpected tasks JSON: {}", body);
            }

            return out;
        }

        void PostResult(httplib::SSLClient &cli, const std::string &guid,
                        uint64_t taskId, bool success,
                        const std::string &resultJson, const std::string &err)
        {
            if (taskId == 0)
            {
                spdlog::warn("Skip posting result: invalid taskId=0 (success={}, err={})", success, err);
                return;
            }
            nlohmann::json dto = {
                {"taskId", taskId},
                {"success", success},
                {"result", resultJson.empty() ? "{}" : resultJson},
                {"error", err}};
            std::string path = "/api/tasks/" + guid;
            auto body = dto.dump();
            spdlog::debug("POST {} body: {}", path, body);
            auto res = cli.Post(path.c_str(), body, "application/json");
            if (!res)
            {
                spdlog::error("POST {} failed (no response)", path);
                return;
            }
            spdlog::info("POST {} -> HTTP {} body: {}", path, res->status, res->body);
        }

        void EnterDeepSleepUntil(long long stopMs)
        {
            m_sleepStartMs = NowMs();
            m_sleepUntilMs = stopMs;
            spdlog::info("Entering deep sleep until {} ms", stopMs);
            // Stop stream & recording immediately
            if (m_controls.stopStreamNow)
            {
                m_controls.stopStreamNow();
            }
            if (m_controls.stopRecordingNow)
            {
                m_controls.stopRecordingNow();
            } 
        }

        bool EnsureTaskClient()
        {
            // Recompute endpoint pieces
            const auto baseUrl = this->m_cfg->GetBaseUrl();
            Url url = ParseBase(baseUrl);

            bool needReinit = !m_taskClient ||
                              m_taskHost != url.host || m_taskPort != url.port ||
                              (url.scheme == "https") != m_taskHttps;

            // Setup CA/CRT (from enrollment)
            std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
            if (!std::filesystem::exists(ca))
                ca = "/etc/iot/keys/ca_chain.pem";
            const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";

            if (!needReinit)
            {
                // still ok — reuse
                return true;
            }

            try
            {
                auto payload = snoop::device_sec::ReadClientKeyPayloadFromKeyring();
                // pick a writable dir (use /tmp to avoid /run root perms surprises)
                snoop::device_sec::TempFile tf(std::filesystem::temp_directory_path());
                tf.write_all(payload.data(), payload.size());

                // OpenSSL loads the files now; once this returns, the key is in-memory.
                auto cli = MakeClient(url, ca, crt, tf.path);
                spdlog::info("Creating new client because of change");
                // tf destructor will remove the file at scope end
                // Swap in
                m_taskClient = std::move(cli);
                m_taskHost = url.host;
                m_taskPort = url.port;
                m_taskHttps = (url.scheme == "https");
                m_taskCa = ca;
                m_taskCrt = crt;
                return true;
            }
            catch (const std::exception &e)
            {
                spdlog::error("EnsureTaskClient: failed to set client cert: {}", e.what());
                return false;
            }
        }

        void RunLoop()
        {
            const std::string guid = m_cfg->GetGuid();
            std::mt19937 rng{std::random_device{}()};

            while (!m_stop)
            {
                // (1) Ensure we have a reusable client
                if (!EnsureTaskClient())
                {
                    spdlog::info("Sleep after task client");
                    SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
                    continue;
                }
                //// FOR SSL DEBUG - remove in prod
                if (m_taskClient)
                {
                    long verify_result = m_taskClient->get_openssl_verify_result();
                    if (verify_result == X509_V_OK)
                    {
                        spdlog::info("[mTLS debug] OpenSSL verify: SUCCESS (X509_V_OK)");
                    }
                    else
                    {
                        const char *err_str = X509_verify_cert_error_string(verify_result);
                        spdlog::error("[mTLS debug] OpenSSL verify failed: {} ({})",
                                      err_str ? err_str : "Unknown error", verify_result);
                    }
                }
                /////////////////////////////////

                // (2) Deep-sleep gate
                const auto now = NowMs();
                const auto startMs = m_sleepStartMs.load();
                const auto untilMs = m_sleepUntilMs.load();

                if (untilMs > 0)
                {
                    if (now < startMs)
                    {
                        // not yet sleeping: wait until start, no requests
                        auto waitMs = std::min<long long>(startMs - now, 60'000);
                        std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
                        continue;
                    }
                    if (now >= startMs && now < untilMs)
                    {
                        // sleeping: no requests; ensure we stopped stream/recording on entry
                        if (now - startMs < 1200)
                        {
                            if (m_controls.stopStreamNow)
                                m_controls.stopStreamNow();
                            if (m_controls.stopRecordingNow)
                                m_controls.stopRecordingNow();
                        }
                        auto waitMs = std::min<long long>(untilMs - now, 60'000);
                        std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
                        continue;
                    }
                    if (now >= untilMs)
                    {
                        // wake up
                        m_sleepStartMs = 0;
                        m_sleepUntilMs = 0;
                        spdlog::info("Deep sleep finished; resuming operation");
                    }
                }

                // (3) Not sleeping -> poll tasks
                try
                {
                    const std::string path = "/api/tasks/" + guid;
                    spdlog::info("GET {}", path);
                    auto res = m_taskClient->Get(path.c_str());
                    if (!res)
                    {
                        spdlog::warn("GET {} failed (no response)", path);
                        m_taskClient.reset(); // force rebuild next iter
                    }
                    else if (res->status == 204)
                    {
                        spdlog::debug("No tasks (204).");
                    }
                    else if (res->status >= 200 && res->status < 300)
                    {
                        auto tasks = ParseTasks(res->body);
                        for (const auto &t : tasks)
                        {
                            auto handler = ResolveHandler(t.type);
                            auto [ok, resultJson, err] = handler(t);
                            if (t.id == 0)
                            {
                                spdlog::warn("Won't POST result for task with id=0: type='{}'", t.type);
                                continue;
                            }
                            PostResult(*m_taskClient, guid, t.id, ok, resultJson, err);
                        }
                    }
                    else
                    {
                        spdlog::warn("GET {} -> HTTP {}, body: {}", path, res->status, res->body);
                    }
                }
                catch (const std::exception &e)
                {
                    spdlog::error("Task loop error: {}", e.what());
                    m_taskClient.reset();
                }

                // (4) Backoff before next poll
                SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
            }
        }
    };

} // namespace snoop
