#include <Tactility/settings/WebServerSettings.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/file/File.h>
#include <Tactility/Paths.h>

#include <tactility/log.h>

#include <charconv>
#include <map>
#include <string>

#ifdef ESP_PLATFORM
#include <esp_mac.h>
#include <esp_wifi.h>
#include <esp_random.h>
#else
#include <random>
#endif

namespace tt::settings::webserver {

constexpr auto* TAG = "WebServerSettings";

static std::string getSettingsFilePath() {
    return getUserDataPath() + "/settings/webserver.properties";
}

// Property keys
constexpr auto* KEY_WIFI_ENABLED = "wifiEnabled";
constexpr auto* KEY_WIFI_MODE = "wifiMode";
constexpr auto* KEY_AP_SSID = "apSsid";
constexpr auto* KEY_AP_PASSWORD = "apPassword";
constexpr auto* KEY_AP_OPEN_NETWORK = "apOpenNetwork";
constexpr auto* KEY_AP_CHANNEL = "apChannel";
constexpr auto* KEY_WEBSERVER_ENABLED = "webServerEnabled";
constexpr auto* KEY_WEBSERVER_PORT = "webServerPort";
constexpr auto* KEY_WEBSERVER_AUTH_ENABLED = "webServerAuthEnabled";
constexpr auto* KEY_WEBSERVER_USERNAME = "webServerUsername";
constexpr auto* KEY_WEBSERVER_PASSWORD = "webServerPassword";

std::string generateDefaultApSsid() {
#ifdef ESP_PLATFORM
    uint8_t mac[6];
    if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
        return "Tactility-0000";
    }
    char ssid[16];
    snprintf(ssid, sizeof(ssid), "Tactility-%02X%02X", mac[2] ^ mac[3], mac[4] ^ mac[5]);
    return std::string(ssid);
#else
    return "Tactility-0000";
#endif
}

/**
 * @brief Generate a cryptographically secure random string for credentials
 * @param length The desired length of the string
 * @return A random alphanumeric string
 */
std::string generateRandomCredential(size_t length) {
    static constexpr char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    static constexpr size_t charsetSize = sizeof(charset) - 1;

    std::string result;
    result.reserve(length);

#ifdef ESP_PLATFORM
    for (size_t i = 0; i < length; ++i) {
        uint32_t randomValue = esp_random();
        result += charset[randomValue % charsetSize];
    }
#else
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, charsetSize - 1);
    for (size_t i = 0; i < length; ++i) {
        result += charset[dis(gen)];
    }
#endif

    return result;
}

/**
 * @brief Check if a credential value is empty (needs auto-generation)
 * @param value The credential value to check
 * @return true if the credential is empty and needs generation
 */
static bool isEmptyCredential(const std::string& value) {
    return value.empty();
}

bool load(WebServerSettings& settings) {
    auto settings_path = getSettingsFilePath();
    if (!file::isFile(settings_path)) {
        return false;
    }

    std::map<std::string, std::string> map;
    if (!file::loadPropertiesFile(settings_path, map)) {
        return false;
    }

    // Parse all settings from the map
    auto wifi_enabled = map.find(KEY_WIFI_ENABLED);
    auto wifi_mode = map.find(KEY_WIFI_MODE);
    auto ap_ssid = map.find(KEY_AP_SSID);
    auto ap_password = map.find(KEY_AP_PASSWORD);
    auto ap_open_network = map.find(KEY_AP_OPEN_NETWORK);
    auto ap_channel = map.find(KEY_AP_CHANNEL);
    auto webserver_enabled = map.find(KEY_WEBSERVER_ENABLED);
    auto webserver_port = map.find(KEY_WEBSERVER_PORT);
    auto webserver_auth_enabled = map.find(KEY_WEBSERVER_AUTH_ENABLED);
    auto webserver_username = map.find(KEY_WEBSERVER_USERNAME);
    auto webserver_password = map.find(KEY_WEBSERVER_PASSWORD);

    // WiFi settings
    settings.wifiEnabled = (wifi_enabled != map.end())
        ? (wifi_enabled->second == "1" || wifi_enabled->second == "true")
        : false;  // Default disabled

    settings.wifiMode = (wifi_mode != map.end() && wifi_mode->second == "1")
        ? WiFiMode::AccessPoint
        : WiFiMode::Station;

    auto parseInt = [](const std::string& value, int min, int max, int fallback) -> int {
        int v = 0;
        auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), v);
        if (ec != std::errc{} || v < min || v > max) {
            return fallback;
        }
        return v;
    };

    // AP mode settings
    settings.apSsid = (ap_ssid != map.end() && !ap_ssid->second.empty())
        ? ap_ssid->second
        : generateDefaultApSsid();
    settings.apPassword = (ap_password != map.end()) ? ap_password->second : "";
    settings.apOpenNetwork = (ap_open_network != map.end())
        ? (ap_open_network->second == "1" || ap_open_network->second == "true")
        : false;
    settings.apChannel = (ap_channel != map.end())
        ? static_cast<uint8_t>(parseInt(ap_channel->second, 1, 13, 1))
        : 1;

    // Security: If AP password is empty, generate a strong random password in memory.
    // Skip this if user explicitly wants an open network.
    // Note: We only auto-generate for EMPTY passwords, not user-set ones.
    // This is a read-only function: the generated password is NOT persisted here —
    // callers that want it saved must call save() explicitly.
    if (!settings.apOpenNetwork && isEmptyCredential(settings.apPassword)) {
        LOG_I(TAG, "AP password is empty - generating secure random password (not persisted)");

        // Generate 12-character random password (alphanumeric, ~71 bits of entropy)
        // WPA2 requires 8-63 characters, so 12 is well within range
        settings.apPassword = generateRandomCredential(12);
    }

    // Web server settings
    settings.webServerEnabled = (webserver_enabled != map.end())
        ? (webserver_enabled->second == "1" || webserver_enabled->second == "true")
        : false;

    settings.webServerPort = (webserver_port != map.end())
        ? static_cast<uint16_t>(parseInt(webserver_port->second, 1, 65535, 80))
        : 80;

    // Web server auth settings
    settings.webServerAuthEnabled = (webserver_auth_enabled != map.end())
        ? (webserver_auth_enabled->second == "1" || webserver_auth_enabled->second == "true")
        : false;

    // Load credentials from file, defaulting to empty if not present
    settings.webServerUsername = (webserver_username != map.end()) ? webserver_username->second : "";
    settings.webServerPassword = (webserver_password != map.end()) ? webserver_password->second : "";

    // Security: If auth is enabled but credentials are empty, generate strong random
    // credentials in memory. Note: We only auto-generate for EMPTY credentials, allowing
    // users to set their own.
    // This is a read-only function: the generated credentials are NOT persisted here —
    // callers that want them saved (so they're consistent across reboots) must call
    // save() explicitly.
    if (settings.webServerAuthEnabled &&
        (isEmptyCredential(settings.webServerUsername) || isEmptyCredential(settings.webServerPassword))) {

        LOG_I(TAG, "Auth enabled with empty credentials - generating secure random credentials (not persisted)");

        // Generate 12-character random credentials (alphanumeric, ~71 bits of entropy each)
        settings.webServerUsername = generateRandomCredential(12);
        settings.webServerPassword = generateRandomCredential(12);
    }

    return true;
}

WebServerSettings getDefault() {
    return WebServerSettings{
        .wifiEnabled = false,  // Default WiFi OFF
        .wifiMode = WiFiMode::Station,
        .apSsid = generateDefaultApSsid(),
        .apPassword = "",  // Empty - will be auto-generated on first use (unless apOpenNetwork)
        .apOpenNetwork = false,  // Default to secured network
        .apChannel = 1,
        .webServerEnabled = false,  // Default WebServer OFF for security
        .webServerPort = 80,
        .webServerAuthEnabled = false,  // Auth disabled by default
        .webServerUsername = "",  // Empty - will be generated if auth is enabled
        .webServerPassword = ""   // Empty - will be generated if auth is enabled
    };
}

WebServerSettings loadOrGetDefault() {
    WebServerSettings settings;

    bool loadedFromFlash = load(settings);
    if (!loadedFromFlash) {
        // No properties file yet (e.g. first boot) - use defaults in memory (WiFi OFF,
        // WebServer OFF). Read-only function: does NOT persist these defaults — callers
        // that want them saved must call save() explicitly.
        settings = getDefault();
    }

    return settings;
}

bool save(const WebServerSettings& settings) {
    std::map<std::string, std::string> map;

    // WiFi settings
    map[KEY_WIFI_ENABLED] = settings.wifiEnabled ? "1" : "0";
    map[KEY_WIFI_MODE] = (settings.wifiMode == WiFiMode::AccessPoint) ? "1" : "0";

    // AP mode settings
    map[KEY_AP_SSID] = settings.apSsid;
    map[KEY_AP_PASSWORD] = settings.apPassword;
    map[KEY_AP_OPEN_NETWORK] = settings.apOpenNetwork ? "1" : "0";
    map[KEY_AP_CHANNEL] = std::to_string(settings.apChannel);

    // Web server settings
    map[KEY_WEBSERVER_ENABLED] = settings.webServerEnabled ? "1" : "0";
    map[KEY_WEBSERVER_PORT] = std::to_string(settings.webServerPort);

    // Web server auth settings
    map[KEY_WEBSERVER_AUTH_ENABLED] = settings.webServerAuthEnabled ? "1" : "0";
    map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
    map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;

    auto settings_path = getSettingsFilePath();
    if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
        LOG_E(TAG, "Failed to create parent dir for %s", settings_path.c_str());
        return false;
    }
    return file::savePropertiesFile(settings_path, map);
}

}
