#include <jni.h>
#include <aidl/com/omixlab/mosis/IMosisListener.h>
#include <aidl/com/omixlab/mosis/BnMosisListener.h>
#include <logger.h>
#include <aidl/com/omixlab/mosis/IMosisService.h>
#include <android/binder_ibinder_jni.h>
#include <format>
#include <IUnityGraphics.h>

#include "texture_backend.h"
#include "opengl_backend.h"
#include "vulkan_backend.h"

using namespace aidl::com::omixlab::mosis;
using namespace aidl::android::hardware;

// Global state
std::shared_ptr<IMosisService> g_service;
std::shared_ptr<class ServiceContext> g_context;
std::unique_ptr<ITextureBackend> g_backend;
IUnityInterfaces* g_unityInterfaces = nullptr;
UnityGfxRenderer g_rendererType = kUnityGfxRendererNull;

// Callback function pointers
typedef void (*OnMessageCallback)(const char*);
typedef void (*OnServiceInitializedCallback)(bool success);
typedef void (*OnFrameAvailableCallback)();
typedef void (*OnBufferReadyCallback)();
typedef void (*OnTextureReadyCallback)(void* nativeTexturePtr, int width, int height, bool isVulkan);

struct NativeCallbacks
{
    OnMessageCallback OnMessage;
    OnServiceInitializedCallback OnServiceInitialized;
    OnFrameAvailableCallback OnFrameAvailable;
    OnBufferReadyCallback OnBufferReady;
    OnTextureReadyCallback OnTextureReady;
};
NativeCallbacks g_callbacks{};

class ServiceContext : public BnMosisListener
{
    AHardwareBuffer* m_hwbuffer = nullptr;

public:
    ndk::ScopedAStatus onServiceInitialized(bool in_success) override
    {
        Logger::Log("onServiceInitialized");
        if (g_callbacks.OnMessage)
            g_callbacks.OnMessage("onServiceInitialized");
        if (g_callbacks.OnServiceInitialized)
            g_callbacks.OnServiceInitialized(in_success);
        return ndk::ScopedAStatus::ok();
    }

    ndk::ScopedAStatus onFrameAvailable() override
    {
        Logger::Log("onFrameAvailable");
        if (g_callbacks.OnMessage)
            g_callbacks.OnMessage("onFrameAvailable");
        if (g_callbacks.OnFrameAvailable)
            g_callbacks.OnFrameAvailable();
        return ndk::ScopedAStatus::ok();
    }

    ndk::ScopedAStatus onBufferAvailable(const HardwareBuffer &in_buffer) override
    {
        Logger::Log("onBufferAvailable");
        if (g_callbacks.OnMessage)
            g_callbacks.OnMessage("onBufferAvailable");
        m_hwbuffer = in_buffer.get();
        AHardwareBuffer_acquire(m_hwbuffer);
        if (g_callbacks.OnBufferReady)
            g_callbacks.OnBufferReady();
        return ndk::ScopedAStatus::ok();
    }

    bool CreateTexture()
    {
        if (!m_hwbuffer)
        {
            Logger::Log("CreateTexture: No hardware buffer available");
            return false;
        }

        if (!g_backend)
        {
            Logger::Log("CreateTexture: No backend available");
            return false;
        }

        if (!g_backend->Create(m_hwbuffer))
        {
            Logger::Log("CreateTexture: Backend Create failed");
            return false;
        }

        // Notify C# with texture info
        if (g_callbacks.OnTextureReady)
        {
            g_callbacks.OnTextureReady(
                g_backend->GetNativeTexturePtr(),
                g_backend->GetWidth(),
                g_backend->GetHeight(),
                g_backend->IsVulkan()
            );
        }

        return true;
    }

    void UpdateTexture()
    {
        if (g_backend)
        {
            g_backend->Update();
        }
    }
};

// Unity Plugin Interface
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
    g_unityInterfaces = unityInterfaces;

    IUnityGraphics* graphics = unityInterfaces->Get<IUnityGraphics>();
    if (!graphics)
    {
        Logger::Log("UnityPluginLoad: IUnityGraphics not available");
        return;
    }

    g_rendererType = graphics->GetRenderer();
    Logger::Log(std::format("UnityPluginLoad: Renderer type = {}", static_cast<int>(g_rendererType)));

    // Try to create Vulkan backend first
    if (g_rendererType == kUnityGfxRendererVulkan)
    {
        auto vulkanBackend = std::make_unique<VulkanTextureBackend>();
        if (vulkanBackend->Initialize(unityInterfaces))
        {
            g_backend = std::move(vulkanBackend);
            Logger::Log("UnityPluginLoad: Using Vulkan backend");
            return;
        }
        Logger::Log("UnityPluginLoad: Vulkan initialization failed, falling back to OpenGL");
    }

    // Fall back to OpenGL for GLES renderer (Unity 6+ only supports GLES 3.0+)
    if (g_rendererType == kUnityGfxRendererOpenGLES30)
    {
        g_backend = std::make_unique<OpenGLTextureBackend>();
        Logger::Log("UnityPluginLoad: Using OpenGL backend");
    }
    else
    {
        Logger::Log(std::format("UnityPluginLoad: Unsupported renderer type {}", static_cast<int>(g_rendererType)));
    }
}

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginUnload()
{
    Logger::Log("UnityPluginUnload");

    // Clean up backend
    if (g_backend)
    {
        g_backend->Destroy();
        g_backend.reset();
    }

    g_unityInterfaces = nullptr;
    g_rendererType = kUnityGfxRendererNull;
}

// Native callback setters
extern "C" void SetNativeCallbacks(const NativeCallbacks& ptr)
{
    g_callbacks = ptr;
}

// Touch input forwarding
extern "C" void SendTouchDown(float x, float y)
{
    if (g_service)
    {
        g_service->onTouchDown(x, y);
    }
}

extern "C" void SendTouchMove(float x, float y)
{
    if (g_service)
    {
        g_service->onTouchMove(x, y);
    }
}

extern "C" void SendTouchUp(float x, float y)
{
    if (g_service)
    {
        g_service->onTouchUp(x, y);
    }
}

// Unity render thread callbacks
static void UNITY_INTERFACE_API OnInitBackendRenderThread(int eventId)
{
    Logger::Log("OnInitBackendRenderThread");

    // For OpenGL, we need to initialize GLAD on the render thread
    if (!g_backend->IsVulkan())
    {
        if (!OpenGLTextureBackend::InitializeGLAD())
        {
            Logger::Log("OnInitBackendRenderThread: Failed to initialize GLAD");
            return;
        }
    }

    if (g_context)
    {
        g_context->CreateTexture();
    }
}

static void UNITY_INTERFACE_API OnUpdateTextureRenderThread(int eventId)
{
    if (g_context)
    {
        g_context->UpdateTexture();
    }
}

extern "C" UnityRenderingEvent InitBackend()
{
    return OnInitBackendRenderThread;
}

extern "C" UnityRenderingEvent UpdateTexture()
{
    return OnUpdateTextureRenderThread;
}

// Legacy compatibility - redirect to new names
extern "C" UnityRenderingEvent InitGLAD()
{
    return InitBackend();
}

// JNI entry point from Kotlin
extern "C"
JNIEXPORT void JNICALL
Java_com_omixlab_mosis_unity_MyKotlinPlugin_serviceConnected(JNIEnv *env, jobject thiz,
    jobject binder)
{
    AIBinder* pBinder = AIBinder_fromJavaBinder(env, binder);
    const ndk::SpAIBinder spBinder(pBinder);
    g_service = IMosisService::fromBinder(spBinder);
    Logger::Log("Service Connected");

    g_context = ndk::SharedRefBase::make<ServiceContext>();
    bool result{};
    g_service->initOS(g_context, &result);
    Logger::Log(std::format("InitOS returned {}", result));
}
