#include <dlfcn.h>
#include <android/log.h>
#include <unistd.h>
#include <cstring>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
#include <mutex>
#include <atomic>
#include <chrono>
#include <fstream>

#include "camera_hal/types.h"
#include "camera_hal/ICameraProvider.h"
#include "camera_hal/ICameraDevice.h"
#include "camera_hal/ICameraDeviceSession.h"

#define LOG_TAG "CameraHook"
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

static void* g_real_hal_handle = nullptr;
static bool g_hook_initialized = false;

static const char* APEX_HAL_PATH =
    "/apex/com.google.pixel.camera.hal/lib64/libgooglecamerahal.so";

struct StreamInfo {
    int32_t id;
    int32_t width;
    int32_t height;
    int32_t format;
    int32_t direction;
    int32_t usage;
    int32_t data_space;
    int32_t hal_format;
    int32_t stride;
};

struct HookState {
    std::map<std::string, void*> open_devices;
    std::map<void*, void*> device_sessions;
    bool virtual_camera_enabled = false;
    int32_t target_stream_format = camera_hal::FORMAT_YUV_420_888;

    std::mutex stream_mutex;
    std::map<int32_t, StreamInfo> stream_registry;
    int32_t yuv_preview_stream_id = -1;
    int32_t yuv_preview_width = 0;
    int32_t yuv_preview_height = 0;

    std::atomic<int64_t> frame_count{0};
    std::atomic<int64_t> injected_count{0};
    std::chrono::steady_clock::time_point fps_start;
    std::atomic<int64_t> fps_frame_count{0};

    static constexpr const char* CONFIG_PATH = "/data/local/camera_magic/config.txt";
};

static HookState g_hook_state;

static bool load_real_hal() {
    if (g_real_hal_handle) {
        return true;
    }

    g_real_hal_handle = dlopen(APEX_HAL_PATH, RTLD_NOW | RTLD_LOCAL);
    if (!g_real_hal_handle) {
        ALOGE("Failed to load real HAL: %s", dlerror());
        return false;
    }

    ALOGI("Loaded real HAL from %s", APEX_HAL_PATH);
    return true;
}

static void* resolve_real_symbol(const char* symbol) {
    if (!g_real_hal_handle) {
        return nullptr;
    }
    void* addr = dlsym(g_real_hal_handle, symbol);
    if (!addr) {
        ALOGW("Symbol not found in real HAL: %s", symbol);
    }
    return addr;
}

static bool check_virtual_camera_enabled() {
    std::ifstream config(g_hook_state.CONFIG_PATH);
    if (!config.is_open()) {
        return false;
    }
    std::string line;
    while (std::getline(config, line)) {
        if (line.find("virtual_camera=1") != std::string::npos ||
            line.find("enabled=true") != std::string::npos) {
            return true;
        }
    }
    return false;
}

static bool inject_video_frame(int32_t stream_id, void* buffer_handle,
                                int32_t width, int32_t height) {
    // Task 7/9 will implement: read from shared memory ring buffer,
    // lock AHardwareBuffer, copy YUV data, unlock.
    // Returns true if frame was injected, false if no frame available.
    (void)stream_id;
    (void)buffer_handle;
    (void)width;
    (void)height;
    return false;
}

static void update_fps_counter() {
    auto now = std::chrono::steady_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
        now - g_hook_state.fps_start).count();
    if (elapsed >= 1) {
        int64_t frames = g_hook_state.fps_frame_count.exchange(0);
        if (frames > 0) {
            ALOGI("[FPS] %.1f fps (%lld frames in %llds)",
                  (double)frames / elapsed, (long long)frames, (long long)elapsed);
        }
        g_hook_state.fps_start = now;
    }
}

class CameraDeviceSessionHook {
public:
    void* real_session;

    explicit CameraDeviceSessionHook(void* real)
        : real_session(real) {
        ALOGI("[SessionHook] Created for session %p", real);
    }

    int32_t configureStreams(
        const camera_hal::Stream* streams,
        int32_t stream_count,
        camera_hal::StreamConfigurationMode mode,
        camera_hal::HalStreamConfiguration* out_config) {

        ALOGI("[SessionHook] configureStreams: %d streams, mode=%d",
              stream_count, mode);

        {
            std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
            g_hook_state.stream_registry.clear();
            g_hook_state.yuv_preview_stream_id = -1;

            for (int32_t i = 0; i < stream_count; i++) {
                const auto& s = streams[i];
                StreamInfo info;
                info.id = s.id;
                info.width = s.width;
                info.height = s.height;
                info.format = s.format;
                info.direction = s.direction;
                info.usage = s.usage;
                info.data_space = s.data_space;
                info.hal_format = 0;
                info.stride = 0;

                g_hook_state.stream_registry[s.id] = info;

                ALOGI("[SessionHook]   Stream[%d]: id=%d %dx%d fmt=%d dir=%d usage=0x%x",
                      i, s.id, s.width, s.height, s.format, s.direction, s.usage);

                if (s.format == camera_hal::FORMAT_YUV_420_888 &&
                    s.direction == camera_hal::STREAM_OUTPUT &&
                    g_hook_state.yuv_preview_stream_id == -1) {
                    g_hook_state.yuv_preview_stream_id = s.id;
                    g_hook_state.yuv_preview_width = s.width;
                    g_hook_state.yuv_preview_height = s.height;
                    ALOGI("[SessionHook]   -> YUV preview stream identified (id=%d)", s.id);
                }
            }
        }

        ALOGI("[SessionHook] Stream registry: %zu streams, YUV preview id=%d",
              g_hook_state.stream_registry.size(),
              g_hook_state.yuv_preview_stream_id);

        return 0;
    }

    int32_t processCaptureRequest(
        const camera_hal::CaptureRequest* requests,
        int32_t request_count,
        int32_t* out_num_request_processed) {

        int64_t total = g_hook_state.frame_count.fetch_add(request_count) + request_count;

        bool enabled = check_virtual_camera_enabled();
        if (enabled != g_hook_state.virtual_camera_enabled) {
            g_hook_state.virtual_camera_enabled = enabled;
            ALOGI("[SessionHook] Virtual camera %s", enabled ? "ENABLED" : "DISABLED");
        }

        for (int32_t r = 0; r < request_count; r++) {
            const auto& req = requests[r];

            for (int32_t b = 0; b < req.output_buffer_count; b++) {
                const auto& buf = req.output_buffers[b];
                int32_t sid = buf.stream_id;

                if (enabled && sid == g_hook_state.yuv_preview_stream_id) {
                    StreamInfo* info = nullptr;
                    {
                        std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
                        auto it = g_hook_state.stream_registry.find(sid);
                        if (it != g_hook_state.stream_registry.end()) {
                            info = &it->second;
                        }
                    }

                    if (info && buf.handle) {
                        bool injected = inject_video_frame(
                            sid, buf.handle, info->width, info->height);
                        if (injected) {
                            g_hook_state.injected_count.fetch_add(1);
                            g_hook_state.fps_frame_count.fetch_add(1);
                            update_fps_counter();
                        }
                    }
                }
            }

            if (total % 300 == 0) {
                ALOGI("[SessionHook] Frames: %lld total, %lld injected",
                      (long long)total,
                      (long long)g_hook_state.injected_count.load());
            }
        }

        if (out_num_request_processed) {
            *out_num_request_processed = request_count;
        }
        return 0;
    }

    int32_t flush() {
        ALOGI("[SessionHook] flush called");
        return 0;
    }

    int32_t close() {
        ALOGI("[SessionHook] close called");
        return 0;
    }
};

class CameraDeviceHook {
public:
    void* real_device;
    std::string camera_id;

    CameraDeviceHook(void* real, const char* id)
        : real_device(real), camera_id(id ? id : "unknown") {
        ALOGI("[DeviceHook] Created for device '%s' (%p)", camera_id.c_str(), real);
    }

    int32_t open(void* callback) {
        ALOGI("[DeviceHook] open called for '%s'", camera_id.c_str());
        return 0;
    }

    int32_t getCameraCharacteristics(void* characteristics) {
        ALOGI("[DeviceHook] getCameraCharacteristics for '%s'", camera_id.c_str());
        return 0;
    }

    int32_t close() {
        ALOGI("[DeviceHook] close called for '%s'", camera_id.c_str());
        g_hook_state.open_devices.erase(camera_id);
        return 0;
    }
};

class CameraProviderHook {
public:
    void* real_provider;

    explicit CameraProviderHook(void* real)
        : real_provider(real) {
        ALOGI("[ProviderHook] Created for provider %p", real);
    }

    int32_t getCameraIdList(char*** camera_ids, int32_t* count) {
        ALOGI("[ProviderHook] getCameraIdList called");
        return 0;
    }

    int32_t getCameraDeviceInterface(
        const char* camera_id,
        void** out_device) {
        ALOGI("[ProviderHook] getCameraDeviceInterface: '%s'", camera_id);
        return 0;
    }

    int32_t openSession(
        const char* camera_id,
        void* callback,
        void** out_session) {
        ALOGI("[ProviderHook] openSession: '%s'", camera_id);
        return 0;
    }
};

__attribute__((constructor))
static void camera_hook_init() {
    if (g_hook_initialized) {
        return;
    }

    ALOGI("=== CameraHook LD_PRELOAD library loaded ===");
    ALOGI("PID: %d, Process: camera provider", getpid());

    if (!load_real_hal()) {
        ALOGE("Cannot load real HAL, hook will not function");
        return;
    }

    g_hook_initialized = true;
    ALOGI("CameraHook initialized successfully");
}

__attribute__((destructor))
static void camera_hook_deinit() {
    if (g_real_hal_handle) {
        dlclose(g_real_hal_handle);
        g_real_hal_handle = nullptr;
    }
    ALOGI("CameraHook unloaded");
}

extern "C" {

__attribute__((visibility("default")))
void* dlopen(const char* filename, int flags) {
    typedef void* (*real_dlopen_t)(const char*, int);
    static real_dlopen_t real_dlopen = nullptr;

    if (!real_dlopen) {
        real_dlopen = (real_dlopen_t)::dlsym(RTLD_NEXT, "dlopen");
    }

    if (filename && strstr(filename, "libgooglecamerahal.so")) {
        ALOGI("[dlopen] Intercepted: %s", filename);
        if (!load_real_hal()) {
            return nullptr;
        }
        return g_real_hal_handle;
    }

    return real_dlopen(filename, flags);
}

__attribute__((visibility("default")))
void* dlsym(void* handle, const char* symbol) {
    typedef void* (*real_dlsym_t)(void*, const char*);
    static real_dlsym_t real_dlsym = nullptr;

    if (!real_dlsym) {
        real_dlsym = (real_dlsym_t)::dlsym(RTLD_NEXT, "dlsym");
    }

    void* result = real_dlsym(handle, symbol);

    if (result && g_hook_initialized && symbol) {
        if (strstr(symbol, "CameraProvider") ||
            strstr(symbol, "CameraDevice") ||
            strstr(symbol, "createProvider") ||
            strstr(symbol, "getProvider")) {
            ALOGI("[dlsym] HAL symbol intercepted: %s -> %p", symbol, result);
        }
    }

    return result;
}

} // extern "C"
