#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>

#include "../include/chroma.h"

#define IPC_MAX_MSG 4096
#define IPC_RESP_OK "ok\n"
#define IPC_RESP_ERR "err %s\n"

static int parse_set_command(const char *cmd, char *output_name, size_t name_sz,
                             char *image_path, size_t path_sz) {
  char action[64];
  int n = sscanf(cmd, "%63s", action);
  if (n != 1)
    return -1;

  if (strcmp(action, "set") == 0) {
    const char *rest = cmd + 4;
    while (*rest == ' ')
      rest++;

    const char *name_start = rest;
    while (*rest && *rest != ' ')
      rest++;
    size_t name_len = (size_t)(rest - name_start);
    if (name_len == 0 || name_len >= name_sz)
      return -1;
    memcpy(output_name, name_start, name_len);
    output_name[name_len] = '\0';

    while (*rest == ' ')
      rest++;
    const char *path_start = rest;
    while (*rest && *rest != '\n' && *rest != '\r')
      rest++;
    size_t path_len = (size_t)(rest - path_start);
    if (path_len == 0 || path_len >= path_sz)
      return -1;
    memcpy(image_path, path_start, path_len);
    image_path[path_len] = '\0';
    return 0;
  }

  return -1;
}

static int parse_set_all_command(const char *cmd, char *image_path,
                                 size_t path_sz) {
  char action[64];
  int n = sscanf(cmd, "%63s", action);
  if (n != 1)
    return -1;

  if (strcmp(action, "set_all") == 0) {
    const char *rest = cmd + 8;
    while (*rest == ' ')
      rest++;
    const char *path_start = rest;
    while (*rest && *rest != '\n' && *rest != '\r')
      rest++;
    size_t path_len = (size_t)(rest - path_start);
    if (path_len == 0 || path_len >= path_sz)
      return -1;
    memcpy(image_path, path_start, path_len);
    image_path[path_len] = '\0';
    return 0;
  }

  return -1;
}

static int parse_clear_command(const char *cmd, char *output_name,
                               size_t name_sz, char *hex_color,
                               size_t color_sz) {
  (void)name_sz;
  char action[64];
  int n = sscanf(cmd, "%63s", action);
  if (n != 1)
    return -1;

  if (strcmp(action, "clear") == 0) {
    const char *rest = cmd + 6;
    while (*rest == ' ')
      rest++;

    if (*rest == '\0' || *rest == '\n' || *rest == '\r') {
      output_name[0] = '\0';
      hex_color[0] = '\0';
      return 0;
    }

    const char *arg1_start = rest;
    while (*rest && *rest != ' ')
      rest++;
    size_t arg1_len = (size_t)(rest - arg1_start);

    while (*rest == ' ')
      rest++;

    if (*rest == '\0' || *rest == '\n' || *rest == '\r') {
      memcpy(hex_color, arg1_start, arg1_len);
      hex_color[arg1_len] = '\0';
      output_name[0] = '\0';
      return 0;
    }

    memcpy(output_name, arg1_start, arg1_len);
    output_name[arg1_len] = '\0';

    const char *color_start = rest;
    while (*rest && *rest != '\n' && *rest != '\r')
      rest++;
    size_t color_len = (size_t)(rest - color_start);
    if (color_len >= color_sz)
      return -1;
    memcpy(hex_color, color_start, color_len);
    hex_color[color_len] = '\0';
    return 0;
  }

  return -1;
}

static uint32_t parse_hex_color(const char *hex, bool *valid) {
  *valid = false;
  if (!hex || hex[0] == '\0')
    return 0xFF000000;

  size_t len = strlen(hex);
  if (len != 6 && len != 8)
    return 0xFF000000;

  for (size_t i = 0; i < len; i++) {
    if (!((hex[i] >= '0' && hex[i] <= '9') ||
          (hex[i] >= 'a' && hex[i] <= 'f') || (hex[i] >= 'A' && hex[i] <= 'F')))
      return 0xFF000000;
  }

  unsigned int r, g, b, a = 0xFF;
  if (len == 6) {
    sscanf(hex, "%2x%2x%2x", &r, &g, &b);
  } else {
    sscanf(hex, "%2x%2x%2x%2x", &r, &g, &b, &a);
  }

  *valid = true;
  return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |
         (uint32_t)b;
}

static int send_response(int fd, const char *response) {
  size_t len = strlen(response);
  ssize_t written = write(fd, response, len);
  return (written == (ssize_t)len) ? 0 : -1;
}

static int handle_command(chroma_state_t *state, int client_fd,
                          const char *command) {
  chroma_log("DEBUG", "IPC command: %s", command);

  if (strcmp(command, "ping") == 0) {
    return send_response(client_fd, "pong\n");
  }

  if (strcmp(command, "reload") == 0) {
    const char *cfg = state->config_file[0] != '\0' ? state->config_file : NULL;
    int ret = chroma_reload_config(state, cfg);
    if (ret != CHROMA_OK) {
      return send_response(client_fd, "err config_reload_failed\n");
    }
    return send_response(client_fd, IPC_RESP_OK);
  }

  if (strncmp(command, "set_all ", 8) == 0 || strcmp(command, "set_all") == 0) {
    char image_path[MAX_PATH_LEN];
    if (parse_set_all_command(command, image_path, sizeof(image_path)) != 0) {
      return send_response(client_fd, "err invalid_syntax\n");
    }
    int ret = chroma_set_wallpaper_for_all(state, image_path);
    if (ret != CHROMA_OK) {
      return send_response(client_fd, "err failed_to_set\n");
    }
    return send_response(client_fd, IPC_RESP_OK);
  }

  if (strncmp(command, "set ", 4) == 0) {
    char output_name[256];
    char image_path[MAX_PATH_LEN];
    if (parse_set_command(command, output_name, sizeof(output_name), image_path,
                          sizeof(image_path)) != 0) {
      return send_response(client_fd, "err invalid_syntax\n");
    }
    int ret = chroma_set_wallpaper_for_output(state, output_name, image_path);
    if (ret != CHROMA_OK) {
      return send_response(client_fd, "err failed_to_set\n");
    }
    return send_response(client_fd, IPC_RESP_OK);
  }

  if (strncmp(command, "clear", 5) == 0) {
    char output_name[256];
    char hex_color[10];
    if (parse_clear_command(command, output_name, sizeof(output_name),
                            hex_color, sizeof(hex_color)) != 0) {
      return send_response(client_fd, "err invalid_syntax\n");
    }
    if (output_name[0] != '\0') {
      int ret =
          chroma_set_solid_color_for_output(state, output_name, hex_color);
      if (ret != CHROMA_OK) {
        return send_response(client_fd, "err failed_to_set\n");
      }
    } else {
      int ret = chroma_set_solid_color_for_all(state, hex_color);
      if (ret != CHROMA_OK) {
        return send_response(client_fd, "err failed_to_set\n");
      }
    }
    return send_response(client_fd, IPC_RESP_OK);
  }

  return send_response(client_fd, "err unknown_command\n");
}

int chroma_ipc_init(chroma_state_t *state) {
  if (!state || !state->config.ipc_enabled) {
    return CHROMA_OK;
  }

  const char *socket_path = state->config.ipc_socket_path;
  if (socket_path[0] == '\0') {
    chroma_log("WARN", "IPC enabled but no socket path configured");
    return CHROMA_OK;
  }

  struct sockaddr_un addr;
  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;

  size_t path_len = strlen(socket_path);
  if (path_len >= sizeof(addr.sun_path)) {
    chroma_log("ERROR", "IPC socket path too long (%zu >= %zu)", path_len,
               sizeof(addr.sun_path));
    return CHROMA_ERROR_CONFIG;
  }
  memcpy(addr.sun_path, socket_path, path_len);

  struct stat st;
  if (lstat(socket_path, &st) == 0) {
    if (!S_ISSOCK(st.st_mode)) {
      chroma_log("ERROR", "IPC socket path exists but is not a socket: %s",
                 socket_path);
      return CHROMA_ERROR_CONFIG;
    }
    unlink(socket_path);
  }

  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  if (fd < 0) {
    chroma_log("ERROR", "Failed to create IPC socket: %s", strerror(errno));
    return CHROMA_ERROR_INIT;
  }

  if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    chroma_log("ERROR", "Failed to bind IPC socket to %s: %s", socket_path,
               strerror(errno));
    close(fd);
    return CHROMA_ERROR_INIT;
  }

  if (listen(fd, 5) < 0) {
    chroma_log("ERROR", "Failed to listen on IPC socket: %s", strerror(errno));
    close(fd);
    return CHROMA_ERROR_INIT;
  }

  state->ipc_fd = fd;
  chroma_log("INFO", "IPC socket listening on %s", socket_path);
  return CHROMA_OK;
}

void chroma_ipc_cleanup(chroma_state_t *state) {
  if (!state || state->ipc_fd < 0) {
    return;
  }

  close(state->ipc_fd);
  state->ipc_fd = -1;

  if (state->config.ipc_socket_path[0] != '\0') {
    unlink(state->config.ipc_socket_path);
  }

  chroma_log("DEBUG", "IPC socket cleaned up");
}

int chroma_ipc_get_fd(chroma_state_t *state) {
  if (!state || state->ipc_fd < 0) {
    return -1;
  }
  return state->ipc_fd;
}

int chroma_ipc_process(chroma_state_t *state) {
  if (!state || state->ipc_fd < 0) {
    return CHROMA_OK;
  }

  int client_fd = accept(state->ipc_fd, NULL, NULL);
  if (client_fd < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      return CHROMA_OK;
    }
    chroma_log("ERROR", "IPC accept failed: %s", strerror(errno));
    return CHROMA_OK;
  }

  char buffer[IPC_MAX_MSG];
  memset(buffer, 0, sizeof(buffer));

  ssize_t n = read(client_fd, buffer, sizeof(buffer) - 1);
  if (n <= 0) {
    close(client_fd);
    return CHROMA_OK;
  }

  buffer[n] = '\0';

  size_t len = strlen(buffer);
  while (len > 0 && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) {
    buffer[--len] = '\0';
  }

  handle_command(state, client_fd, buffer);

  close(client_fd);
  return CHROMA_OK;
}

int chroma_set_wallpaper_for_output(chroma_state_t *state,
                                    const char *output_name,
                                    const char *image_path) {
  if (!state || !output_name || !image_path) {
    return CHROMA_ERROR_INIT;
  }

  char *expanded = chroma_expand_path(image_path);
  const char *path = expanded ? expanded : image_path;

  if (!chroma_path_exists(path)) {
    chroma_log("ERROR", "Image not found: %s", path);
    if (expanded)
      free(expanded);
    return CHROMA_ERROR_IMAGE;
  }

  chroma_output_t *output = chroma_output_find_by_name(state, output_name);
  if (!output) {
    chroma_log("ERROR", "Output not found: %s", output_name);
    if (expanded)
      free(expanded);
    return CHROMA_ERROR_INIT;
  }

  chroma_image_t *image =
      chroma_image_get_or_load(state, path, output->width, output->height);
  if (!image) {
    chroma_log("ERROR", "Failed to load image for output %s: %s", output_name,
               path);
    if (expanded)
      free(expanded);
    return CHROMA_ERROR_IMAGE;
  }

  if (output->image) {
    chroma_image_release(output->image);
  }
  output->image = image;

  if (state->config.transition_enabled) {
    chroma_transition_init_output(output, state->config.transition_duration_ms);
  }

  if (!output->surface) {
    int ret = chroma_surface_create(state, output);
    if (ret != CHROMA_OK) {
      chroma_log("ERROR", "Failed to create surface for output %s",
                 output_name);
      if (expanded)
        free(expanded);
      return ret;
    }
  }

  int ret = chroma_render_wallpaper(state, output);
  if (ret != CHROMA_OK) {
    chroma_log("ERROR", "Failed to render wallpaper for output %s",
               output_name);
    if (expanded)
      free(expanded);
    return ret;
  }

  if (expanded)
    free(expanded);

  chroma_log("INFO", "Set wallpaper for output %s: %s", output_name, path);
  return CHROMA_OK;
}

int chroma_set_wallpaper_for_all(chroma_state_t *state,
                                 const char *image_path) {
  if (!state || !image_path) {
    return CHROMA_ERROR_INIT;
  }

  char *expanded = chroma_expand_path(image_path);
  const char *path = expanded ? expanded : image_path;

  if (!chroma_path_exists(path)) {
    chroma_log("ERROR", "Image not found: %s", path);
    if (expanded)
      free(expanded);
    return CHROMA_ERROR_IMAGE;
  }

  int success = 0;
  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];
    if (!output->active)
      continue;

    if (chroma_set_wallpaper_for_output(state,
                                        output->name ? output->name : "unknown",
                                        path) == CHROMA_OK) {
      success++;
    }
  }

  if (expanded)
    free(expanded);

  return (success > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
}

int chroma_set_solid_color_for_output(chroma_state_t *state,
                                      const char *output_name,
                                      const char *hex_color) {
  if (!state || !output_name) {
    return CHROMA_ERROR_INIT;
  }

  bool valid = false;
  uint32_t color = parse_hex_color(hex_color, &valid);

  chroma_output_t *output = chroma_output_find_by_name(state, output_name);
  if (!output) {
    chroma_log("ERROR", "Output not found: %s", output_name);
    return CHROMA_ERROR_INIT;
  }

  chroma_image_t *img = NULL;
  char color_path[MAX_PATH_LEN];
  snprintf(color_path, sizeof(color_path), "color:#%08X", color);
  img = chroma_image_find_by_path(state, color_path);
  if (img) {
    img->ref_count++;
    chroma_log("DEBUG", "Reusing solid color image: %s", color_path);
  } else {
    for (int i = 0; i < state->image_count; i++) {
      if (!state->images[i].loaded) {
        img = &state->images[i];
        break;
      }
    }
    if (!img) {
      if (state->image_count >= MAX_OUTPUTS) {
        chroma_log("ERROR", "Maximum number of images reached (%d)",
                   MAX_OUTPUTS);
        return CHROMA_ERROR_MEMORY;
      }
      img = &state->images[state->image_count];
      state->image_count++;
    }
    int load_ret =
        chroma_image_load_color(img, color, output->width, output->height);
    if (load_ret != CHROMA_OK) {
      return load_ret;
    }
  }

  int ret;

  if (output->image) {
    chroma_image_release(output->image);
  }
  output->image = img;

  if (state->config.transition_enabled) {
    chroma_transition_init_output(output, state->config.transition_duration_ms);
  }

  if (!output->surface) {
    ret = chroma_surface_create(state, output);
    if (ret != CHROMA_OK)
      return ret;
  }

  ret = chroma_render_wallpaper(state, output);
  if (ret != CHROMA_OK)
    return ret;

  chroma_log("INFO", "Set solid color for output %s: %s", output_name,
             hex_color ? hex_color : "none");
  return CHROMA_OK;
}

int chroma_set_solid_color_for_all(chroma_state_t *state,
                                   const char *hex_color) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  int success = 0;
  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];
    if (!output->active)
      continue;

    if (chroma_set_solid_color_for_output(
            state, output->name ? output->name : "unknown", hex_color) ==
        CHROMA_OK) {
      success++;
    }
  }

  return (success > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
}
