#include "cortex_recorder.h"

#include <android/log.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <algorithm>
#include <cstdio>

#define TAG "CortexRecorder"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

// Segment binary format magic
static const char SEGMENT_MAGIC[4] = {'C', 'S', 'E', 'G'};
static const uint32_t SEGMENT_VERSION = 1;
static const uint32_t END_MARKER = 0xFFFFFFFF;

// Helper: write raw bytes to fd
static bool writeBytes(int fd, const void* data, size_t size) {
    return write(fd, data, size) == static_cast<ssize_t>(size);
}

template<typename T>
static bool writeVal(int fd, T val) {
    return writeBytes(fd, &val, sizeof(T));
}

void CortexRecorder::Configure(int w, int h, uint32_t sr, uint32_t ch, int abr) {
    std::lock_guard<std::mutex> lock(mutex);
    videoWidth = w;
    videoHeight = h;
    sampleRate = sr;
    audioChannels = ch;
    audioBitrate = abr;
    LOGI("Configured: %dx%d, audio %uHz %uch %dbps", w, h, sr, ch, abr);
}

void CortexRecorder::SetVideoFormat(const uint8_t* sps_pps, uint32_t size) {
    std::lock_guard<std::mutex> lock(mutex);
    videoCodecConfig.assign(sps_pps, sps_pps + size);
    LOGI("Video format set: %u bytes", size);
}

void CortexRecorder::SetAudioFormat(const uint8_t* aac_config, uint32_t size) {
    std::lock_guard<std::mutex> lock(mutex);
    audioCodecConfig.assign(aac_config, aac_config + size);
    LOGI("Audio format set: %u bytes", size);
}

void CortexRecorder::StartSession(const std::string& dir) {
    std::lock_guard<std::mutex> lock(mutex);

    // Create directory
    mkdir(dir.c_str(), 0755);

    sessionDir = dir;
    segmentIndex = 0;
    segmentStartUs = 0;
    currentSamples.clear();
    firstKeyframeData.clear();
    active = true;

    LOGI("Session started: %s", dir.c_str());
}

void CortexRecorder::StopSession() {
    std::lock_guard<std::mutex> lock(mutex);
    if (!active) return;

    // Finalize any partial segment
    if (!currentSamples.empty()) {
        FinalizeCurrentSegment();
    }

    active = false;
    currentSamples.clear();
    firstKeyframeData.clear();
    videoCodecConfig.clear();
    audioCodecConfig.clear();
    LOGI("Session stopped");
}

void CortexRecorder::FeedVideoPacket(const uint8_t* data, uint32_t size,
                                      int64_t timestampUs, bool isKeyframe) {
    std::lock_guard<std::mutex> lock(mutex);
    if (!active) return;

    // On keyframe boundary, check if we should finalize the current segment
    if (isKeyframe && !currentSamples.empty()) {
        int64_t segDuration = timestampUs - segmentStartUs;
        if (segDuration >= SEGMENT_DURATION_US) {
            FinalizeCurrentSegment();
        }
    }

    // Start new segment if empty
    if (currentSamples.empty()) {
        segmentStartUs = timestampUs;
    }

    // Store first keyframe data for thumbnail callback
    if (isKeyframe && firstKeyframeData.empty()) {
        firstKeyframeData.assign(data, data + size);
    }

    CortexSampleEntry entry;
    entry.track = 0; // video
    entry.flags = isKeyframe ? 1 : 0;
    entry.size = size;
    entry.timestampUs = timestampUs;
    entry.data.assign(data, data + size);
    currentSamples.push_back(std::move(entry));
}

void CortexRecorder::FeedAudioPacket(const uint8_t* data, uint32_t size,
                                      int64_t timestampUs) {
    std::lock_guard<std::mutex> lock(mutex);
    if (!active) return;

    CortexSampleEntry entry;
    entry.track = 1; // audio
    entry.flags = 0;
    entry.size = size;
    entry.timestampUs = timestampUs;
    entry.data.assign(data, data + size);
    currentSamples.push_back(std::move(entry));
}

void CortexRecorder::SetMaxDurationMinutes(int minutes) {
    std::lock_guard<std::mutex> lock(mutex);
    maxDurationMinutes = minutes;
    LOGI("Max duration set to %d minutes", minutes);
}

void CortexRecorder::SetSegmentCallback(SegmentCallback cb) {
    std::lock_guard<std::mutex> lock(mutex);
    segmentCallback = std::move(cb);
}

bool CortexRecorder::IsActive() const {
    return active;
}

std::string CortexRecorder::MakeSegmentPath(int index) const {
    char buf[64];
    snprintf(buf, sizeof(buf), "seg_%06d.seg", index);
    return sessionDir + "/" + buf;
}

void CortexRecorder::FinalizeCurrentSegment() {
    // Must be called under lock
    if (currentSamples.empty()) return;

    std::string segPath = MakeSegmentPath(segmentIndex);

    int fd = open(segPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        LOGE("Failed to open segment file: %s", segPath.c_str());
        currentSamples.clear();
        firstKeyframeData.clear();
        return;
    }

    // Compute duration
    int64_t lastTs = currentSamples.back().timestampUs;
    int64_t segDuration = lastTs - segmentStartUs;
    uint32_t sampleCount = static_cast<uint32_t>(currentSamples.size());

    // Write header
    writeBytes(fd, SEGMENT_MAGIC, 4);
    writeVal<uint32_t>(fd, SEGMENT_VERSION);
    writeVal<uint32_t>(fd, static_cast<uint32_t>(videoWidth));
    writeVal<uint32_t>(fd, static_cast<uint32_t>(videoHeight));

    // SPS/PPS
    uint32_t spsPpsSize = static_cast<uint32_t>(videoCodecConfig.size());
    writeVal<uint32_t>(fd, spsPpsSize);
    if (spsPpsSize > 0) writeBytes(fd, videoCodecConfig.data(), spsPpsSize);

    // AAC config
    uint32_t aacConfigSize = static_cast<uint32_t>(audioCodecConfig.size());
    writeVal<uint32_t>(fd, aacConfigSize);
    if (aacConfigSize > 0) writeBytes(fd, audioCodecConfig.data(), aacConfigSize);

    // Timing
    writeVal<int64_t>(fd, segmentStartUs);
    writeVal<int64_t>(fd, segDuration);
    writeVal<uint32_t>(fd, sampleCount);

    // Write samples
    for (const auto& sample : currentSamples) {
        writeVal<uint8_t>(fd, sample.track);
        writeVal<uint8_t>(fd, sample.flags);
        writeVal<uint32_t>(fd, sample.size);
        writeVal<int64_t>(fd, sample.timestampUs);
        writeBytes(fd, sample.data.data(), sample.size);
    }

    // End marker
    writeVal<uint32_t>(fd, END_MARKER);

    close(fd);

    LOGI("Segment finalized: %s (%u samples, %.1fs)",
         segPath.c_str(), sampleCount, segDuration / 1000000.0);

    // Callback with segment path + first keyframe data for thumbnail
    if (segmentCallback && !firstKeyframeData.empty()) {
        // Copy callback and data before calling (callback may be slow)
        auto cb = segmentCallback;
        auto kfData = firstKeyframeData;
        // Call outside lock would be better, but we're already under lock.
        // The callback should be fast (just posts to another thread).
        cb(segPath, kfData.data(), static_cast<uint32_t>(kfData.size()));
    }

    segmentIndex++;
    currentSamples.clear();
    firstKeyframeData.clear();

    // Trim old segments
    TrimOldSegments();
}

void CortexRecorder::TrimOldSegments() {
    // Must be called under lock
    // Scan all .seg files in session dir, read their duration from header,
    // and delete oldest when total exceeds maxDurationMinutes

    struct SegInfo {
        std::string path;
        int64_t durationUs;
        int index;
    };

    DIR* dir = opendir(sessionDir.c_str());
    if (!dir) return;

    std::vector<SegInfo> segments;
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        std::string name = entry->d_name;
        if (name.size() < 4 || name.substr(name.size() - 4) != ".seg") continue;

        std::string path = sessionDir + "/" + name;

        // Parse index from filename (seg_NNNNNN.seg)
        int idx = 0;
        if (name.size() >= 14 && name.substr(0, 4) == "seg_") {
            idx = std::atoi(name.substr(4, 6).c_str());
        }

        // Read duration from header
        int fd = open(path.c_str(), O_RDONLY);
        if (fd < 0) continue;

        // Skip: magic(4) + version(4) + width(4) + height(4) = 16
        // Then sps_pps_size(4) + N bytes, aac_config_size(4) + N bytes
        // Then seg_start_us(8), seg_duration_us(8)
        char magic[4];
        if (read(fd, magic, 4) != 4 || memcmp(magic, SEGMENT_MAGIC, 4) != 0) {
            close(fd);
            continue;
        }

        uint32_t ver, w, h, spsSz, aacSz;
        read(fd, &ver, 4);
        read(fd, &w, 4);
        read(fd, &h, 4);
        read(fd, &spsSz, 4);
        lseek(fd, spsSz, SEEK_CUR);
        read(fd, &aacSz, 4);
        lseek(fd, aacSz, SEEK_CUR);

        int64_t startUs, durationUs;
        read(fd, &startUs, 8);
        read(fd, &durationUs, 8);
        close(fd);

        segments.push_back({path, durationUs, idx});
    }
    closedir(dir);

    if (segments.empty()) return;

    // Sort by index (ascending = oldest first)
    std::sort(segments.begin(), segments.end(),
              [](const SegInfo& a, const SegInfo& b) { return a.index < b.index; });

    // Sum total duration from newest backward
    int64_t maxUs = static_cast<int64_t>(maxDurationMinutes) * 60 * 1000000LL;
    int64_t totalUs = 0;

    // Walk from newest to oldest, mark keep boundary
    int keepFrom = static_cast<int>(segments.size()); // index into segments where we start keeping
    for (int i = static_cast<int>(segments.size()) - 1; i >= 0; i--) {
        totalUs += segments[i].durationUs;
        if (totalUs > maxUs) {
            // This segment pushes us over — delete from here backward
            break;
        }
        keepFrom = i;
    }

    // Delete segments before keepFrom
    int deleted = 0;
    for (int i = 0; i < keepFrom; i++) {
        unlink(segments[i].path.c_str());

        // Also delete associated thumbnail
        std::string thumbPath = sessionDir + "/";
        char thumbName[64];
        snprintf(thumbName, sizeof(thumbName), "thumb_%06d.jpg", segments[i].index);
        thumbPath += thumbName;
        unlink(thumbPath.c_str());

        deleted++;
    }

    if (deleted > 0) {
        LOGI("Trimmed %d old segments (total duration %.0fs, max %dm)",
             deleted, totalUs / 1000000.0, maxDurationMinutes);
    }
}
