#include "spdk_engine/io_engine.h"

#include "common/config.h"
#include "proto/ipc_proto.h"

#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>



struct ipc_client_ctx {
    int fd;
    uint8_t rx_buf[ZVFS_IPC_BUF_SIZE];
    size_t rx_len;
};

static __thread struct ipc_client_ctx g_ipc_tls = {
    .fd = -1,
    .rx_len = 0,
};

static const char *zvfs_ipc_socket_path(void) {
    const char *path = getenv("ZVFS_SOCKET_PATH");
    if (path && path[0] != '\0') {
        return path;
    }

    path = getenv("ZVFS_IPC_SOCKET_PATH");
    if (path && path[0] != '\0') {
        return path;
    }

    return ZVFS_IPC_DEFAULT_SOCKET_PATH;
}

static void ipc_close_conn(struct ipc_client_ctx *ctx) {
    if (ctx->fd >= 0) {
        close(ctx->fd);
    }
    ctx->fd = -1;
    ctx->rx_len = 0;
}

static int ipc_connect(struct ipc_client_ctx *ctx) {
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0) {
        return -1;
    }

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, zvfs_ipc_socket_path(), sizeof(addr.sun_path) - 1);

    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        int saved = errno;
        close(fd);
        errno = saved;
        return -1;
    }

    ctx->fd = fd;
    ctx->rx_len = 0;
    return 0;
}

static int ipc_ensure_connected(struct ipc_client_ctx *ctx) {
    if (ctx->fd >= 0) {
        return 0;
    }
    return ipc_connect(ctx);
}

static int write_all(int fd, const uint8_t *buf, size_t len) {
    size_t off = 0;
    while (off < len) {
        ssize_t n = write(fd, buf + off, len - off);
        if (n > 0) {
            off += (size_t)n;
            continue;
        }

        if (n < 0 && errno == EINTR) {
            continue;
        }

        if (n == 0) {
            errno = EPIPE;
        }
        return -1;
    }
    return 0;
}

static int try_pop_resp(struct ipc_client_ctx *ctx, struct zvfs_resp *resp) {
    size_t consumed = zvfs_deserialize_resp(ctx->rx_buf, ctx->rx_len, resp);
    if (consumed == 0) {
        return 0;
    }

    if (consumed < ctx->rx_len) {
        memmove(ctx->rx_buf, ctx->rx_buf + consumed, ctx->rx_len - consumed);
    }
    ctx->rx_len -= consumed;
    return 1;
}

static int read_into_rx(struct ipc_client_ctx *ctx) {
    while (1) {
        if (ctx->rx_len >= sizeof(ctx->rx_buf)) {
            errno = EOVERFLOW;
            return -1;
        }

        ssize_t n = read(ctx->fd, ctx->rx_buf + ctx->rx_len, sizeof(ctx->rx_buf) - ctx->rx_len);
        if (n > 0) {
            ctx->rx_len += (size_t)n;
            return 0;
        }

        if (n == 0) {
            errno = ECONNRESET;
            return -1;
        }

        if (errno == EINTR) {
            continue;
        }

        return -1;
    }
}

static int recv_one_resp(struct ipc_client_ctx *ctx, struct zvfs_resp *resp_out) {
    while (1) {
        struct zvfs_resp resp;
        memset(&resp, 0, sizeof(resp));

        int has_resp = try_pop_resp(ctx, &resp);
        if (has_resp == 1) {
            *resp_out = resp;
            return 0;
        }

        if (read_into_rx(ctx) != 0) {
            return -1;
        }

        if (ctx->rx_len == sizeof(ctx->rx_buf)) {
            struct zvfs_resp probe;
            memset(&probe, 0, sizeof(probe));
            if (zvfs_deserialize_resp(ctx->rx_buf, ctx->rx_len, &probe) == 0) {
                errno = EPROTO;
                return -1;
            }
            if (probe.data) {
                free(probe.data);
            }
        }
    }
}

static int set_errno_by_status(int status) {
    if (status == 0) {
        return 0;
    }

    if (status < 0) {
        errno = -status;
    } else {
        errno = status;
    }

    if (errno == 0) {
        errno = EIO;
    }
    return -1;
}

uint8_t tx[ZVFS_IPC_BUF_SIZE];

static int ipc_do_req(struct zvfs_req *req, struct zvfs_resp *resp_out) {
    struct ipc_client_ctx *ctx = &g_ipc_tls;

    if (ipc_ensure_connected(ctx) != 0) {
        return -1;
    }

    size_t tx_len = zvfs_serialize_req(req, tx, sizeof(tx));
    if (tx_len == 0) {
        errno = EMSGSIZE;
        return -1;
    }

    if (write_all(ctx->fd, tx, tx_len) != 0) {
        ipc_close_conn(ctx);
        return -1;
    }

    if (recv_one_resp(ctx, resp_out) != 0) {
        ipc_close_conn(ctx);
        return -1;
    }

    return set_errno_by_status(resp_out->status);
}

int io_engine_init(void) {
    return 0;
}

int blob_create(uint64_t size_hint, uint64_t *blob_id_out, uint64_t *handle_id_out) {
    if (!blob_id_out || !handle_id_out) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_CREATE;
    req.size_hint = size_hint;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    *blob_id_out = resp.blob_id;
    *handle_id_out = resp.handle_id;

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_open(uint64_t blob_id, uint64_t *handle_id_out) {
    if (!handle_id_out) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_OPEN;
    req.blob_id = blob_id;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    *handle_id_out = resp.handle_id;

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_write_ex(uint64_t handle_id, uint64_t offset, const void *buf, size_t len, uint32_t write_flags) {
    if (len == 0) {
        return 0;
    }
    if (!buf || handle_id == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_WRITE;
    req.handle_id = handle_id;
    req.offset = offset;
    req.length = (uint64_t)len;
    req.write_flags = write_flags;
    req.data = (void *)buf;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.bytes_written != (uint64_t)len) {
        if (resp.data) {
            free(resp.data);
        }
        errno = EIO;
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_write(uint64_t handle_id, uint64_t offset, const void *buf, size_t len) {
    return blob_write_ex(handle_id, offset, buf, len, 0);
}

int blob_read(uint64_t handle_id, uint64_t offset, void *buf, size_t len) {
    if (len == 0) {
        return 0;
    }
    if (!buf || handle_id == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_READ;
    req.handle_id = handle_id;
    req.offset = offset;
    req.length = (uint64_t)len;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (!resp.data || resp.length != (uint64_t)len) {
        if (resp.data) {
            free(resp.data);
        }
        errno = EPROTO;
        return -1;
    }

    memcpy(buf, resp.data, len);
    free(resp.data);
    return 0;
}

int blob_resize(uint64_t handle_id, uint64_t new_size) {
    if (handle_id == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_RESIZE;
    req.handle_id = handle_id;
    req.size_hint = new_size;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_sync_md(uint64_t handle_id) {
    if (handle_id == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_SYNC_MD;
    req.handle_id = handle_id;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_close(uint64_t handle_id) {
    if (handle_id == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_CLOSE;
    req.handle_id = handle_id;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_delete(uint64_t blob_id) {
    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_DELETE;
    req.blob_id = blob_id;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_add_ref(uint64_t handle_id, uint32_t ref_delta) {
    if (handle_id == 0 || ref_delta == 0) {
        errno = EINVAL;
        return -1;
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_ADD_REF;
    req.handle_id = handle_id;
    req.ref_delta = ref_delta;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        return -1;
    }

    if (resp.data) {
        free(resp.data);
    }
    return 0;
}

int blob_add_ref_batch(const uint64_t *handle_ids, const uint32_t *ref_deltas, uint32_t count) {
    uint32_t i;
    struct zvfs_add_ref_item *items = NULL;

    if (!handle_ids || !ref_deltas || count == 0) {
        errno = EINVAL;
        return -1;
    }

    items = calloc(count, sizeof(*items));
    if (!items) {
        errno = ENOMEM;
        return -1;
    }

    for (i = 0; i < count; i++) {
        if (handle_ids[i] == 0 || ref_deltas[i] == 0) {
            free(items);
            errno = EINVAL;
            return -1;
        }
        items[i].handle_id = handle_ids[i];
        items[i].ref_delta = ref_deltas[i];
    }

    struct zvfs_req req;
    memset(&req, 0, sizeof(req));
    req.opcode = ZVFS_OP_ADD_REF_BATCH;
    req.add_ref_count = count;
    req.add_ref_items = items;

    struct zvfs_resp resp;
    memset(&resp, 0, sizeof(resp));

    if (ipc_do_req(&req, &resp) != 0) {
        free(items);
        return -1;
    }

    free(items);
    if (resp.data) {
        free(resp.data);
    }
    return 0;
}
