#pragma once

#include <QtGlobal>
#include <algorithm>
#include <cstdlib>
#include <format>
#include <iostream>
#include <ostream>
#include <qcoreapplication.h>
#include <string>

enum class LogLevel {
  DEBUG_LEVEL_TRACE = 0,
  DEBUG_LEVEL_INFO = 1,
  DEBUG_LEVEL_WARN = 2,
  DEBUG_LEVEL_ERROR = 3,
  DEBUG_LEVEL_OFF = 4
};

inline LogLevel getLogLevelFromEnv() {
  const char *env = std::getenv("DEBUG_LEVEL");
  if (!env) {
    return LogLevel::DEBUG_LEVEL_ERROR;
  }

  std::string level(env);
  std::transform(level.begin(), level.end(), level.begin(), ::tolower);

  if (level == "trace" || level == "0")
    return LogLevel::DEBUG_LEVEL_TRACE;
  if (level == "info" || level == "1")
    return LogLevel::DEBUG_LEVEL_INFO;
  if (level == "warn" || level == "2")
    return LogLevel::DEBUG_LEVEL_WARN;
  if (level == "error" || level == "3")
    return LogLevel::DEBUG_LEVEL_ERROR;
  if (level == "off" || level == "4")
    return LogLevel::DEBUG_LEVEL_OFF;

  return LogLevel::DEBUG_LEVEL_ERROR;
}

namespace Logger {

inline LogLevel getCurrentLevel() {
  static const LogLevel currentLevel = getLogLevelFromEnv();
  return currentLevel;
}

inline void info(std::string_view message) {
  if (static_cast<int>(LogLevel::DEBUG_LEVEL_INFO) <
      static_cast<int>(getCurrentLevel())) {
    return;
  }
  std::cout << "\033[32m[Info]\033[0m " << message << std::endl;
}

inline void warn(std::string_view message) {
  if (static_cast<int>(LogLevel::DEBUG_LEVEL_WARN) <
      static_cast<int>(getCurrentLevel())) {
    return;
  }
  std::clog << "\033[33m[Warn]\033[0m " << message << std::endl;
}

inline void error(std::string_view message) {
  if (static_cast<int>(LogLevel::DEBUG_LEVEL_ERROR) <
      static_cast<int>(getCurrentLevel())) {
    return;
  }
  std::cerr << "\033[31m[Error] " << message << "\033[0m" << std::endl;
}

} // namespace Logger

inline void MessageHandler(QtMsgType type, const QMessageLogContext &context,
                           const QString &msg) {
  static const LogLevel currentLevel = getLogLevelFromEnv();

  LogLevel msgLevel;
  std::string prefix;
  std::string color;
  std::string reset = "\033[0m";

  switch (type) {
  case QtDebugMsg:
    msgLevel = LogLevel::DEBUG_LEVEL_TRACE;
    prefix = "[Trace]";
    color = "\033[36m";
    break;
  case QtInfoMsg:
    msgLevel = LogLevel::DEBUG_LEVEL_INFO;
    prefix = "[Info]";
    color = "\033[32m";
    break;
  case QtWarningMsg:
    msgLevel = LogLevel::DEBUG_LEVEL_WARN;
    prefix = "[Warn]";
    color = "\033[33m";
    break;
  case QtCriticalMsg:
    msgLevel = LogLevel::DEBUG_LEVEL_ERROR;
    prefix = "[Error]";
    color = "\033[31m";
    break;
  case QtFatalMsg:
    msgLevel = LogLevel::DEBUG_LEVEL_ERROR;
    prefix = "[Fatal]";
    color = "\033[35m";
    break;
  }

  if (static_cast<int>(msgLevel) < static_cast<int>(currentLevel)) {
    return;
  }

  std::string context_str = " ";
  if (context.file && context.line) {
    context_str = std::format(" ({}:{}) ", context.file, context.line);
  }
  
  std::cerr << color << prefix << reset << context_str << msg.toStdString() << std::endl;

  if (type == QtFatalMsg) {
    std::abort();
  }
}
