#include "WiFiManager.hpp"

namespace cyd {

WiFiManager::WiFiManager(SettingsManager& settings) : settings_(settings) {
}

void WiFiManager::begin() {
    WiFi.disconnect(true);
    delay(100);

    if (settings_.hasWifiCredentials()) {
        Serial.printf("WiFiManager: loaded SSID '%s', connecting...\n", settings_.getWifiSsid().c_str());
        startStation_();
    } else {
        Serial.println("WiFiManager: no credentials stored, starting setup AP");
        startAccessPoint_();
    }
}

void WiFiManager::update() {
    switch (state_) {
        case State::Uninitialized:
            break;

        case State::Connecting:
            if (WiFi.status() == WL_CONNECTED) {
                Serial.printf("WiFiManager: connected, IP %s\n", WiFi.localIP().toString().c_str());
                state_ = State::StationConnected;
                if (modeCallback_) {
                    modeCallback_(false);
                }
                return;
            }
            if (millis() - connectStartMillis_ > kConnectTimeoutMs) {
                Serial.println("WiFiManager: station connection timeout");
                WiFi.disconnect(true);
                state_ = State::StationFailed;
                startAccessPoint_();
            }
            break;

        case State::StationConnected:
            if (WiFi.status() != WL_CONNECTED) {
                Serial.println("WiFiManager: station lost connection, retrying");
                state_ = State::Connecting;
                connectStartMillis_ = millis();
                WiFi.reconnect();
            }
            break;

        case State::AccessPoint:
        case State::FallbackToAp:
            if (apRunning_) {
                dnsServer_.processNextRequest();
            }
            break;

        case State::StationFailed:
            // Should have transitioned to AP above.
            break;
    }
}

String WiFiManager::getIpAddress() const {
    if (isApMode()) {
        return WiFi.softAPIP().toString();
    }
    if (state_ == State::StationConnected) {
        return WiFi.localIP().toString();
    }
    return "0.0.0.0";
}

void WiFiManager::enterSetupMode() {
    if (state_ == State::StationConnected || state_ == State::Connecting) {
        WiFi.disconnect(true);
    }
    startAccessPoint_();
}

void WiFiManager::configureAndConnect(const String& ssid, const String& pass) {
    settings_.setWifiSsid(ssid);
    settings_.setWifiPass(pass);
    settings_.save();
    stopAccessPoint_();
    startStation_();
}

void WiFiManager::startStation_() {
    state_ = State::Connecting;
    connectStartMillis_ = millis();

    WiFi.mode(WIFI_STA);
    WiFi.begin(settings_.getWifiSsid().c_str(), settings_.getWifiPass().c_str());

    if (modeCallback_) {
        modeCallback_(false);
    }
}

void WiFiManager::startAccessPoint_() {
    state_ = State::AccessPoint;

    WiFi.mode(WIFI_AP);
    WiFi.softAP(kApSsid, kApPass);

    IPAddress ip = WiFi.softAPIP();
    Serial.printf("WiFiManager: AP '%s' started at %s\n", kApSsid, ip.toString().c_str());

    // DNS redirect: any hostname requested while connected to the AP resolves
    // to the CYD. The HTTP server in ApiServer serves the setup page at /.
    dnsServer_.start(53, "*", ip);
    apRunning_ = true;

    if (modeCallback_) {
        modeCallback_(true);
    }
}

void WiFiManager::stopAccessPoint_() {
    apRunning_ = false;
    dnsServer_.stop();
    WiFi.softAPdisconnect(true);
}

} // namespace cyd
