#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

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

// Global state pointer for signal handling
static chroma_state_t *g_state = NULL;
static char *g_config_file = NULL;

// Signal handler implementation
static void signal_handler_impl(int sig) {
  switch (sig) {
  case SIGTERM:
  case SIGINT:
    chroma_log("INFO", "Received signal %d (%s), shutting down gracefully", sig,
               (sig == SIGTERM) ? "SIGTERM" : "SIGINT");
    chroma_should_quit = 1;
    if (g_state) {
      g_state->running = false;
    }
    break;
  case SIGHUP:
    chroma_log("INFO", "Received SIGHUP, reloading configuration");
    if (g_state && g_config_file) {
      chroma_reload_config(g_state, g_config_file);
    }
    break;
  case SIGPIPE:
    // Ignore SIGPIPE - we'll handle broken pipes in read/write calls
    break;
  default:
    chroma_log("WARN", "Received unexpected signal: %d", sig);
    break;
  }
}

// Set up signal handlers
void chroma_handle_signals(void) {
  struct sigaction sa;

  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = signal_handler_impl;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  // Install signal handlers
  if (sigaction(SIGTERM, &sa, NULL) == -1) {
    chroma_log("ERROR", "Failed to install SIGTERM handler: %s",
               strerror(errno));
  }

  if (sigaction(SIGINT, &sa, NULL) == -1) {
    chroma_log("ERROR", "Failed to install SIGINT handler: %s",
               strerror(errno));
  }

  if (sigaction(SIGHUP, &sa, NULL) == -1) {
    chroma_log("ERROR", "Failed to install SIGHUP handler: %s",
               strerror(errno));
  }

  // Ignore SIGPIPE
  sa.sa_handler = SIG_IGN;
  if (sigaction(SIGPIPE, &sa, NULL) == -1) {
    chroma_log("ERROR", "Failed to ignore SIGPIPE: %s", strerror(errno));
  }

  chroma_log("DEBUG", "Signal handlers installed");
}

// Set global state for signal handling
void chroma_set_signal_state(chroma_state_t *state, const char *config_file) {
  g_state = state;

  free(g_config_file);
  g_config_file = config_file ? strdup(config_file) : NULL;
}

// Clean up signal handling resources
void chroma_cleanup_signals(void) {
  g_state = NULL;
  free(g_config_file);
  g_config_file = NULL;
}

// Expand environment variables in a string
static char *expand_env_vars(const char *str) {
  if (!str || strchr(str, '$') == NULL) {
    return strdup(str);
  }

  char *result = strdup("");
  if (!result) {
    return NULL;
  }

  const char *p = str;
  while (*p) {
    if (*p == '$') {
      p++;
      if (*p == '{') {
        // ${VAR} format
        p++;
        const char *end = strchr(p, '}');
        if (!end) {
          // No closing brace, treat as literal
          char *tmp = realloc(result, strlen(result) + 2);
          if (!tmp) {
            free(result);
            return NULL;
          }
          result = tmp;
          strcat(result, "${");
          break;
        }

        size_t var_len = (size_t)(end - p);
        char *var_name = malloc(var_len + 1);
        if (!var_name) {
          free(result);
          return NULL;
        }
        strncpy(var_name, p, var_len);
        var_name[var_len] = '\0';

        const char *var_value = getenv(var_name);
        if (var_value) {
          char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
          if (!tmp) {
            free(var_name);
            free(result);
            return NULL;
          }
          result = tmp;
          strcat(result, var_value);
        }

        free(var_name);
        p = end + 1;
      } else {
        // $VAR format
        const char *start = p;
        while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
          p++;
        }

        if (p == start) {
          // Not a valid variable name, treat $ as literal
          char *tmp = realloc(result, strlen(result) + 2);
          if (!tmp) {
            free(result);
            return NULL;
          }
          result = tmp;
          strcat(result, "$");
        } else {
          size_t var_len = (size_t)(p - start);
          char *var_name = malloc(var_len + 1);
          if (!var_name) {
            free(result);
            return NULL;
          }
          strncpy(var_name, start, var_len);
          var_name[var_len] = '\0';

          const char *var_value = getenv(var_name);
          if (var_value) {
            char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
            if (!tmp) {
              free(var_name);
              free(result);
              return NULL;
            }
            result = tmp;
            strcat(result, var_value);
          }

          free(var_name);
        }
      }
    } else {
      // Regular character
      size_t len = strlen(result);
      char *tmp = realloc(result, len + 2);
      if (!tmp) {
        free(result);
        return NULL;
      }
      result = tmp;
      result[len] = *p;
      result[len + 1] = '\0';
      p++;
    }
  }

  return result;
}

// Expand tilde and environment variables in path
char *chroma_expand_path(const char *path) {
  if (!path) {
    return NULL;
  }

  // First expand environment variables
  char *env_expanded = expand_env_vars(path);
  if (!env_expanded) {
    return NULL;
  }

  // Then expand tilde if present
  if (env_expanded[0] != '~') {
    return env_expanded;
  }

  const char *home;
  if (env_expanded[1] == '/' || env_expanded[1] == '\0') {
    // ~/... or just ~
    home = getenv("HOME");
    if (!home) {
      struct passwd *pw = getpwuid(getuid());
      if (pw) {
        home = pw->pw_dir;
      }
    }
    if (!home) {
      chroma_log("ERROR", "Could not determine home directory");
      free(env_expanded);
      return strdup(path); // Return original path as fallback
    }

    size_t home_len = strlen(home);
    size_t path_len = strlen(env_expanded);
    char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0
    if (!expanded) {
      chroma_log("ERROR", "Failed to allocate memory for path expansion");
      free(env_expanded);
      return strdup(path);
    }

    strcpy(expanded, home);
    if (env_expanded[1] == '/') {
      strcat(expanded, env_expanded + 1);
    }

    free(env_expanded);
    return expanded;
  } else {
    // ~user/...
    const char *slash = strchr(env_expanded, '/');
    size_t user_len =
        slash ? (size_t)(slash - env_expanded - 1) : strlen(env_expanded) - 1;

    char *username = malloc(user_len + 1);
    if (!username) {
      free(env_expanded);
      return strdup(path);
    }

    strncpy(username, env_expanded + 1, user_len);
    username[user_len] = '\0';

    struct passwd *pw = getpwnam(username);

    if (!pw) {
      chroma_log("ERROR", "User not found: %s", username);
      free(username);
      free(env_expanded);
      return strdup(path);
    }

    free(username);

    size_t home_len = strlen(pw->pw_dir);
    size_t remaining_len = slash ? strlen(slash) : 0;
    char *expanded = malloc(home_len + remaining_len + 1);
    if (!expanded) {
      free(env_expanded);
      return strdup(path);
    }

    strcpy(expanded, pw->pw_dir);
    if (slash) {
      strcat(expanded, slash);
    }

    free(env_expanded);
    return expanded;
  }
}

// Create directory recursively
int chroma_mkdir_recursive(const char *path, mode_t mode) {
  if (!path) {
    return -1;
  }

  char *path_copy = strdup(path);
  if (!path_copy) {
    return -1;
  }

  char *p = path_copy;

  // Skip leading slashes
  while (*p == '/') {
    p++;
  }

  while (*p) {
    // Find next slash
    while (*p && *p != '/') {
      p++;
    }

    if (*p) {
      *p = '\0';

      // Create directory
      if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
        chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
                   strerror(errno));
        free(path_copy);
        return -1;
      }

      *p = '/';
      p++;
    }
  }

  // Create final directory
  if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
    chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
               strerror(errno));
    free(path_copy);
    return -1;
  }

  free(path_copy);
  return 0;
}

// Get configuration directory
char *chroma_get_config_dir(void) {
  const char *xdg_config = getenv("XDG_CONFIG_HOME");

  if (xdg_config) {
    char *config_dir = malloc(strlen(xdg_config) + strlen("/chroma") + 1);
    if (config_dir) {
      sprintf(config_dir, "%s/chroma", xdg_config);
      return config_dir;
    }
  }

  const char *home = getenv("HOME");
  if (home) {
    char *config_dir = malloc(strlen(home) + strlen("/.config/chroma") + 1);
    if (config_dir) {
      sprintf(config_dir, "%s/.config/chroma", home);
      return config_dir;
    }
  }

  return strdup("/etc/chroma"); // Fallback
}

// Check if path exists
bool chroma_path_exists(const char *path) {
  if (!path) {
    return false;
  }

  struct stat st;
  return (stat(path, &st) == 0);
}

// Check if path is a regular file
bool chroma_is_regular_file(const char *path) {
  if (!path) {
    return false;
  }

  struct stat st;
  if (stat(path, &st) != 0) {
    return false;
  }

  return S_ISREG(st.st_mode);
}

// Check if path is a directory
bool chroma_is_directory(const char *path) {
  if (!path) {
    return false;
  }

  struct stat st;
  if (stat(path, &st) != 0) {
    return false;
  }

  return S_ISDIR(st.st_mode);
}

// Get file size
long chroma_get_file_size(const char *path) {
  if (!path) {
    return -1;
  }

  struct stat st;
  if (stat(path, &st) != 0) {
    return -1;
  }

  return st.st_size;
}

// Get file extension
const char *chroma_get_file_extension(const char *path) {
  if (!path) {
    return NULL;
  }

  const char *last_dot = strrchr(path, '.');
  if (!last_dot || last_dot == path) {
    return NULL;
  }

  return last_dot + 1;
}

// Get current time in milliseconds
long long chroma_get_time_ms(void) {
  struct timeval tv;
  if (gettimeofday(&tv, NULL) != 0) {
    return 0;
  }

  return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// Sleep for specified milliseconds
void chroma_sleep_ms(long ms) {
  if (ms <= 0) {
    return;
  }

  struct timespec ts;
  ts.tv_sec = ms / 1000;
  ts.tv_nsec = (ms % 1000) * 1000000;

  nanosleep(&ts, NULL);
}

// Format memory size in human readable format
void chroma_format_memory_size(size_t bytes, char *buffer, size_t buffer_size) {
  if (!buffer || buffer_size == 0) {
    return;
  }

  const char *units[] = {"B", "KB", "MB", "GB", "TB"};
  const int num_units = sizeof(units) / sizeof(units[0]);

  double size = (double)bytes;
  int unit_index = 0;

  while (size >= 1024.0 && unit_index < num_units - 1) {
    size /= 1024.0;
    unit_index++;
  }

  if (unit_index == 0) {
    snprintf(buffer, buffer_size, "%.0f %s", size, units[unit_index]);
  } else {
    snprintf(buffer, buffer_size, "%.2f %s", size, units[unit_index]);
  }
}

// Cleanup utility functions
void chroma_utils_cleanup(void) { chroma_cleanup_signals(); }

// Returns width of monitor accounting for transform
int32_t chroma_output_width(chroma_output_t *output) {
  if (output->transform & 1)
    return output->height;
  else
    return output->width;
}

// Returns height of monitor accounting for transform
int32_t chroma_output_height(chroma_output_t *output) {
  if (output->transform & 1)
    return output->width;
  else
    return output->height;
}
