// src/Services/AudioStreamService.h
#pragma once

#include <atomic>
#include <chrono>
#include <filesystem>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <array>

#include <spdlog/spdlog.h>

#include "WhipClient.h"
#include "ConfigService.h"

namespace snoop {

class AudioStreamService {
    std::shared_ptr<ConfigService> m_cfg;

    // WHIP
    std::unique_ptr<WhipClient> m_whip;
    std::mutex m_whipMutex;
    std::string m_tmpKeyPath; // temp key extracted from keyctl (deleted on Stop)

public:
    explicit AudioStreamService(std::shared_ptr<ConfigService> cfg)
        : m_cfg(std::move(cfg)) {}

    ~AudioStreamService() {
        StopWhip();
    }

    // Feed raw PCM (float32 interleaved), frames = samples per channel
    void OnOpus(const unsigned char* opusData, size_t opusBytes, int pcmFramesPerChannel) {
        std::lock_guard lk(m_whipMutex);
        if (m_whip) m_whip->PushOpus(opusData, opusBytes, pcmFramesPerChannel);
    }

    bool StartWhip(const std::string& whipUrl, int sampleRate=48000, int channels=1) {
        std::lock_guard lk(m_whipMutex);
        if (m_whip) {
            spdlog::info("WHIP already started");
            return true;
        }
        if (!m_cfg) {
            spdlog::error("StartWhip requires ConfigService");
            return false;
        }

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

        // extract client key via keyctl
        auto tmpKey = ExtractClientKeyTemp();
        if (!tmpKey) {
            spdlog::error("Cannot extract client key for WHIP (keyctl user iot-client-key)");
            return false;
        }

        WhipClient::Params p{
            .whipUrl   = whipUrl,
            .caPath    = ca.string(),
            .crtPath   = crt.string(),
            .keyPath   = tmpKey->string(),
            .sampleRate= sampleRate,
            .channels  = channels
        };
        m_whip = std::make_unique<WhipClient>(p);
        try {
            m_whip->Start();
            spdlog::info("WHIP started");
            m_tmpKeyPath = *tmpKey;
            return true;
        } catch (const std::exception& e) {
            spdlog::error("WHIP start failed: {}", e.what());
            std::error_code ec; std::filesystem::remove(*tmpKey, ec);
            m_whip.reset();
            return false;
        }
    }

    void StopWhip() {
        std::lock_guard lk(m_whipMutex);
        if (m_whip) {
            m_whip->Stop();
            m_whip.reset();
        }
        if (!m_tmpKeyPath.empty()) {
            std::error_code ec; std::filesystem::remove(m_tmpKeyPath, ec);
            m_tmpKeyPath.clear();
        }
    }

private:
    static std::optional<std::filesystem::path> ExtractClientKeyTemp() {
        auto exec = [](const std::string& cmd) {
            std::array<char, 4096> buf{};
            std::string out;
            FILE* pipe = popen((cmd + " 2>&1").c_str(), "r");
            if (!pipe) return std::string{};
            while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) out.append(buf.data());
            pclose(pipe);
            return out;
        };
        auto trim = [](std::string s){
            auto b=s.find_first_not_of(" \t\r\n"), e=s.find_last_not_of(" \t\r\n");
            return (b==std::string::npos) ? std::string{} : s.substr(b, e-b+1);
        };

        std::string id = trim(exec("keyctl search @s user iot-client-key | tail -n1"));
        if (id.empty()) return std::nullopt;

        char tmpl[] = "/run/iot-whip-keyXXXXXX";
        int fd = mkstemp(tmpl);
        if (fd < 0) return std::nullopt;
        close(fd);
        std::filesystem::path p(tmpl);
        exec("keyctl pipe " + id + " > " + p.string());
        if (!std::filesystem::exists(p) || std::filesystem::file_size(p) == 0) {
            std::error_code ec; std::filesystem::remove(p, ec);
            return std::nullopt;
        }
        return p;
    }
};

} // namespace snoop
