#include <fstream>
#include <spdlog/spdlog.h>

#include "AudioEncoders/OpusEncoder.h"
#include "AudioWriters/OggAudioWriter.h"
#include "Concepts/AudioEncderConcept.h"
#include "Concepts/AudioWriterConcept.h"
#include "Services/AudioStreamService.h"
#include "Services/AudioWriterService.h"
#include "Services/ConfigService.h"
#include "Services/EnrollmentService.h"
#include "Services/DeviceControlService.h"
#include "Security/TlsKeyUtil.h"

#ifdef USE_ALSA_ADAPTER
#include "AudioAdapters/AlsaAudioAdapter.h"
using AudioAdapter = snoop::AlsaAudioAdapter;
#else
#include "AudioAdapters/PortAudioAdapter.h"
using AudioAdapter = snoop::PortAudioAdapter;
#endif

// sudo apt-get install libasound2-dev

namespace snoop
{
    template <AudioEncoderConcept TAudioEncoder, AudioWriterConcept TAudioWriter>
    void Main()
    {
        try
        {
            int sampleRate = 48000;
            int channels = 1;
            int framesPerBuffer = 2880;

            auto configService = std::make_shared<ConfigService>("config.json");

            // ---- FIRST-RUN ENROLLMENT ----
            // EnrollmentService enroll(configService);
            // const bool didEnroll = enroll.EnsureEnrolled();
            // if (didEnroll)
            // {
            //     spdlog::info("First-run enrollment completed.");
            // }
            auto enrollSvc = std::make_shared<EnrollmentService>(configService);
            enrollSvc->EnsureEnrolled();
            try
            {
                enrollSvc->RenewCertificate(false);
            }
            catch (const std::exception &e)
            {
                spdlog::warn("Auto-renew check failed: {}", e.what());
            }

            // Ensure client key is in kernel keyring for this session
            try
            {
                bool loaded = snoop::device_sec::EnsureClientKeyInKernelKeyring(configService->GetGuid());
                if (loaded)
                {
                    spdlog::info("Main: client key loaded into kernel keyring for this session");
                }
                else
                {
                    spdlog::info("Main: client key already present in kernel keyring");
                }
            }
            catch (const std::exception &e)
            {
                spdlog::warn("Main: failed to ensure client key in keyring: {}", e.what());
            }

            auto writerService = std::make_shared<AudioWriterService>(configService, "records");

            // WHIP-only streamer
            AudioStreamService streamer(configService);

            // Encoder: keep writing to local Ogg via writerService; (no Socket.IO send)
            TAudioEncoder encoder(sampleRate, channels, framesPerBuffer, [&](auto input, auto size) -> int
                                  {
                writerService->WriteAudioData(input, size, framesPerBuffer);
                streamer.OnOpus(reinterpret_cast<const unsigned char*>(input),static_cast<size_t>(size),framesPerBuffer);
                return paContinue; });

            snoop::DeviceControlService::Controls controls{
                .stopStreamNow = [&]()
                { streamer.StopWhip(); },
                .stopRecordingNow = [&]()
                { writerService->StopRecordingNow(); }};

            snoop::DeviceControlService::Handlers handlers{};
            static std::unique_ptr<snoop::DeviceControlService> g_taskSvc;

            // ------------------------------
            {

                handlers.onStartStream = [&](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("start_stream payload: {}", t.payload.dump());
                    // TODO: start your streaming pipeline using payload["whipUrl"], etc.
                    std::string whipUrl;
                    try
                    {
                        if (t.payload.contains("whipUrl"))
                        {
                            whipUrl = t.payload.at("whipUrl").get<std::string>();
                        }
                    }
                    catch (...)
                    {
                    }
                    if (whipUrl.empty())
                    {
                        return snoop::DeviceControlService::HandlerResult{false, "{}", "whipUrl missing"};
                    }
                    bool ok = streamer.StartWhip(whipUrl, sampleRate, channels);
                    return snoop::DeviceControlService::HandlerResult{ok, R"({"whip":"started"})", ok ? "" : "failed"};
                };
                handlers.onStopStream = [&](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("stop_stream payload: {}", t.payload.dump());
                    // TODO: stop streaming
                    streamer.StopWhip();
                    return snoop::DeviceControlService::HandlerResult{true, R"({"status":"stopped"})", ""};
                };
                handlers.onStartRecording = [&](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("start_recording payload: {}", t.payload.dump());
                    // TODO: if you gate writes in AudioWriterService, flip to recording mode here
                    writerService->StartRecording();
                    return snoop::DeviceControlService::HandlerResult{true, R"({"recording":"on"})", ""};
                };
                handlers.onStopRecording = [&](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("stop_recording payload: {}", t.payload.dump());
                    // TODO: flip to recording off
                    writerService->StopRecordingGracefully();
                    return snoop::DeviceControlService::HandlerResult{true, R"({"recording":"off"})", ""};
                };
                handlers.onUpdateConfig = [&](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("update_config payload: {}", t.payload.dump());
                    try
                    {
                        if (t.payload.contains("duration"))
                        {
                            auto ms = static_cast<unsigned long long>(t.payload.at("duration").get<int>() * 1000ULL);
                            configService->SetRecordingDuration(ms);
                        }
                        if (t.payload.contains("sleep"))
                        {
                            configService->SetPollingInterwall(t.payload.at("sleep").get<int>());
                        }
                        if (t.payload.contains("jitter"))
                        {
                            configService->SetJitter(t.payload.at("jitter").get<int>());
                        }
                        if (t.payload.contains("endpoint"))
                        {
                            configService->SetBaseUrl(t.payload.at("endpoint").get<std::string>());
                        }
                        return snoop::DeviceControlService::HandlerResult{true, R"({"updated":true})", ""};
                    }
                    catch (const std::exception &e)
                    {
                        return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()};
                    }
                };
                handlers.onSetDeepSleep = [](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("set_deep_sleep payload: {}", t.payload.dump());
                    // TODO: use platform power management or just extend sleep window

                    try
                    {
                        long long startMs = 0, stopMs = 0;
                        if (t.payload.contains("start_ms") && t.payload.contains("stop_ms"))
                        {
                            startMs = t.payload.at("start_ms").get<long long>();
                            stopMs = t.payload.at("stop_ms").get<long long>();
                        }
                        else if (t.payload.contains("start") && t.payload.contains("stop"))
                        {
                            auto s = snoop::DeviceControlService::ParseRfc3339UtcToMs(t.payload["start"].get<std::string>());
                            auto e = snoop::DeviceControlService::ParseRfc3339UtcToMs(t.payload["stop"].get<std::string>());
                            if (!s || !e)
                            {
                                throw std::runtime_error("Invalid RFC3339 times");
                            }
                            startMs = *s;
                            stopMs = *e;
                        }
                        else if (t.payload.contains("start") && t.payload["start"].is_string() && t.payload["start"].get<std::string>() == "now" && t.payload.contains("for_s"))
                        {
                            startMs = snoop::DeviceControlService::NowMs();
                            stopMs = startMs + t.payload["for_s"].get<long long>() * 1000LL;
                        }
                        else
                        {
                            throw std::runtime_error("Missing/invalid start/stop time");
                        }
                        if (stopMs <= startMs)
                        {
                            throw std::runtime_error("stop <= start");
                        }
                        g_taskSvc->ArmDeepSleep(startMs, stopMs);
                        return snoop::DeviceControlService::HandlerResult{
                            true,
                            std::string("{\"start_ms\":") + std::to_string(startMs) + ",\"stop_ms\":" + std::to_string(stopMs) + "}",
                            ""};
                    }
                    catch (const std::exception &e)
                    {
                        return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()};
                    }
                };
                handlers.onRenewCert = [enrollSvc](const snoop::DeviceControlService::Task &t)
                {
                    spdlog::info("renew_cert task received, payload: {}", t.payload.dump());
                    try
                    {
                        bool ok = enrollSvc->RenewCertificate(true); // <- force
                        if (ok)
                        {
                            return snoop::DeviceControlService::HandlerResult{true, R"({"renewed":true})", ""};
                        }
                        return snoop::DeviceControlService::HandlerResult{true, R"({"renewed":false})", ""};
                    }
                    catch (const std::exception &e)
                    {
                        return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()};
                    }
                };
            }

            g_taskSvc = std::make_unique<snoop::DeviceControlService>(configService, handlers, controls);

            while (true)
            {
                std::this_thread::sleep_for(std::chrono::seconds(1));
            }
        }
        catch (const std::exception &ex)
        {
            spdlog::error("Exception: {}", ex.what());
        }
    }
}

int main()
{
    snoop::Main<snoop::OpusEncoder<AudioAdapter>, snoop::OggAudioWriter>();
    return 0;
}