#pragma once

#include "egl_context.h"
#include "composition_pipeline.h"
#include "rtmp_sink.h"

#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
#include <android/hardware_buffer.h>
#include <android/native_window.h>

#include <atomic>
#include <cstdint>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <variant>
#include <vector>

struct VideoFrame {
    AHardwareBuffer* buffer;
    int64_t timestampNs;
    int fenceFd; // -1 if no fence
    int bufferIndex; // pool slot index for release callback
};

struct AudioFrame {
    std::vector<uint8_t> pcmData;
    int64_t timestampNs;
};

struct StreamingStats {
    int64_t videoBitrate = 0;
    int64_t audioBitrate = 0;
    int fps = 0;
    int droppedFrames = 0;
};

/**
 * Streaming engine: imports HardwareBuffers via EGL, encodes with AMediaCodec,
 * and streams via RTMP to one or more destinations.
 *
 * All encoding happens in native code (zero-copy pipeline).
 */
class StreamingEngine {
public:
    using StatsCallback = std::function<void(const StreamingStats&)>;
    using ErrorCallback = std::function<void(int code, const std::string& message)>;
    using BufferReleasedCallback = std::function<void(int bufferIndex)>;

    StreamingEngine();
    ~StreamingEngine();

    /** Configure the engine. Must be called before Start(). */
    bool Configure(int width, int height, int videoBitrate, int audioBitrate,
                   int sampleRate, int channels, int keyframeInterval);

    /** Add an RTMP destination. Returns destination index. */
    int AddDestination(const std::string& rtmpUrl);

    /** Start encoding and streaming. */
    bool Start();

    /** Submit a video frame from HardwareBuffer. Non-blocking. */
    void SubmitVideoFrame(AHardwareBuffer* buffer, int64_t timestampNs, int fenceFd, int bufferIndex);

    /** Submit audio PCM data. Non-blocking. */
    void SubmitAudioFrame(const uint8_t* pcmData, size_t pcmSize, int64_t timestampNs);

    /** Stop encoding and streaming. Blocks until clean shutdown. */
    void Stop();

    /** Set callbacks. */
    void SetStatsCallback(StatsCallback callback);
    void SetErrorCallback(ErrorCallback callback);
    void SetBufferReleasedCallback(BufferReleasedCallback callback);

    bool IsRunning() const { return running.load(); }

    // Preview surface (thread-safe, enqueued for GL thread)
    void SetPreviewSurface(ANativeWindow* window);
    void RemovePreviewSurface();

    // Composition layer management (thread-safe, enqueued for GL thread)
    int AddCompositionLayer(const uint8_t* rgbaData, int w, int h,
                            float posX, float posY, float scaleX, float scaleY,
                            float rotation, float opacity, int zOrder,
                            const std::string& tag);
    void RemoveCompositionLayer(int layerId);
    void UpdateCompositionLayerTransform(int layerId, float posX, float posY,
                                         float scaleX, float scaleY, float rotation);
    void UpdateCompositionLayerOpacity(int layerId, float opacity);
    void SetCompositionLayerEnabled(int layerId, bool enabled);

private:
    // Encoder thread
    void EncoderThreadFunc();
    void ProcessVideoFrame(const VideoFrame& frame);
    void ProcessAudioFrame(const AudioFrame& frame);
    void DrainVideoEncoder();
    void DrainAudioEncoder();
    void UpdateStats();

    // Blit composed texture to a surface (GL_TEXTURE_2D → draw)
    void BlitComposedToSurface(GLuint composedTex, int viewportW, int viewportH);

    // Process pending operations from other threads
    void ProcessPendingPreviewOps();
    void ProcessPendingLayerOps();

    // Config
    int width = 0;
    int height = 0;
    int videoBitrate = 6000000;
    int audioBitrate = 128000;
    int sampleRate = 48000;
    int channels = 2;
    int keyframeInterval = 2;
    int framerate = 30;

    // EGL
    EglContext eglContext;

    // Composition pipeline (FBO-based)
    CompositionPipeline compositionPipeline;

    // Blit resources — OES program (for legacy/unused path)
    GLuint blitProgram = 0;
    GLuint blitVao = 0;
    GLuint blitVbo = 0;

    // Blit FBO program (sampler2D for composed texture → surface)
    GLuint blitFboProgram = 0;

    // Video encoder
    AMediaCodec* videoEncoder = nullptr;
    ANativeWindow* encoderSurface = nullptr;

    // Audio encoder
    AMediaCodec* audioEncoder = nullptr;

    // Audio accumulation buffer — collects PCM and submits in 1024-sample AAC frames
    std::vector<uint8_t> audioPcmBuffer;
    int64_t audioBufferSamplesWritten = 0; // total samples submitted to encoder
    static constexpr int AAC_FRAME_SAMPLES = 1024;
    void FlushAudioBuffer();

    // RTMP sinks (one per destination)
    std::vector<RtmpSink*> sinks;

    // Threading
    std::thread encoderThread;
    std::atomic<bool> running{false};

    // Frame queues (protected by mutex)
    std::mutex videoMutex;
    std::vector<VideoFrame> videoQueue;

    std::mutex audioMutex;
    std::vector<AudioFrame> audioQueue;

    // Preview surface — pending ops from non-GL threads
    struct PreviewSetOp { ANativeWindow* window; };
    struct PreviewRemoveOp {};
    using PreviewOp = std::variant<PreviewSetOp, PreviewRemoveOp>;
    std::mutex previewMutex;
    std::vector<PreviewOp> pendingPreviewOps;
    bool hasPreview = false;

    // Layer ops — pending ops from non-GL threads
    struct LayerAddOp {
        std::vector<uint8_t> rgbaData;
        int w, h;
        float posX, posY, scaleX, scaleY, rotation, opacity;
        int zOrder;
        std::string tag;
        int assignedId;
    };
    struct LayerRemoveOp { int layerId; };
    struct LayerTransformOp { int layerId; float posX, posY, scaleX, scaleY, rotation; };
    struct LayerOpacityOp { int layerId; float opacity; };
    struct LayerEnabledOp { int layerId; bool enabled; };
    using LayerOp = std::variant<LayerAddOp, LayerRemoveOp, LayerTransformOp,
                                  LayerOpacityOp, LayerEnabledOp>;
    std::mutex layerOpMutex;
    std::vector<LayerOp> pendingLayerOps;
    std::atomic<int> nextLayerId{1};

    // Stats
    std::mutex statsMutex;
    StreamingStats currentStats;
    int64_t statsVideoBytes = 0;
    int64_t statsAudioBytes = 0;
    int statsFrameCount = 0;
    int64_t statsLastUpdateNs = 0;

    // Start timestamp for relative timing
    int64_t startTimestampNs = 0;
    bool firstVideoFrame = true;

    // Standby frame timing
    int64_t lastComposeTimeNs = 0;

    // Tracks when the last game video frame was received.
    // Standby frames are only generated after a timeout with no game input.
    int64_t lastGameVideoFrameNs = 0;
    static constexpr int64_t STANDBY_TIMEOUT_NS = 500000000LL; // 500ms

    // Tracks whether external audio has arrived (set by SubmitAudioFrame)
    std::atomic<bool> hasExternalAudioSource{false};

    // Callbacks
    StatsCallback statsCallback;
    ErrorCallback errorCallback;
    BufferReleasedCallback bufferReleasedCallback;

    bool InitVideoEncoder();
    bool InitAudioEncoder();
    bool InitBlitResources();
    void ReleaseBlitResources();
};
