#include <WiFi.h>
#include <WebServer.h>
#include <WiFiManager.h> 
#include <PubSubClient.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

#define LED_PIN 8
const int SENSOR_VCC_PIN = 1; 
const int SOIL_PIN = 2;       
const int ONE_WIRE_BUS = 3;   

const char* mqtt_server = "broker.emqx.io";
const char* mqtt_topic = "leir/ecosystem/multi_live"; 

// ⚠️ 본인의 실제 GitHub Pages 대시보드 주소로 수정해 주세요!
const char* leir_dashboard_url = "https://yersundanny.github.io/testleir/Index"; 

// ⭐ [수정 핵심] WiFiManager를 전역 변수로 선언하여 프로그램이 끝날 때까지 살려둡니다.
WiFiManager wm;

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
String student_id;

WiFiClient espClient;
PubSubClient mqttClient(espClient);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

unsigned long lastMsgTime = 0;
const long msgInterval = 2000; 

bool isWifiConnected = false;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
        String rxValue = pCharacteristic->getValue();
        if (rxValue.length() > 0) {
            if (rxValue[0] == '1') {
                digitalWrite(LED_PIN, LOW);  
            } else if (rxValue[0] == '0') {
                digitalWrite(LED_PIN, HIGH); 
            }
        }
    }
};

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) { deviceConnected = true; };
    void onDisconnect(BLEServer* pServer) { deviceConnected = false; }
};

void generateUniqueID() {
  uint64_t mac = ESP.getEfuseMac();
  uint32_t macLastFour = (uint32_t)(mac >> 32) & 0xFFFF;
  char idBuffer[20];
  sprintf(idBuffer, "Leir_%04X", macLastFour);
  student_id = String(idBuffer);
}

void setup() {
    pinMode(SENSOR_VCC_PIN, OUTPUT);
    digitalWrite(SENSOR_VCC_PIN, HIGH);

    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, HIGH); 

    generateUniqueID();
    sensors.begin();
    sensors.setWaitForConversion(false); 

    // BLE 레이어 초기화
    BLEDevice::init(student_id.c_str()); 
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());
    BLEService *pService = pServer->createService(SERVICE_UUID);
    pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
    pTxCharacteristic->addDescriptor(new BLE2902());
    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
    pRxCharacteristic->setCallbacks(new MyCallbacks());
    pService->start();
    pServer->getAdvertising()->start();
    Serial.println("📡 [BLE] 블루투스 레이어 가동 완료.");

    // ⚠️ 매번 켤 때마다 무조건 와이파이 설정창(Leir_Setup)을 강제로 띄웁니다.
    wm.resetSettings(); 

    // 커스텀 레이아웃 주입
    String custom_html = "<style>"
                         "body { background-color: #f4f7f6; font-family: sans-serif; color: #263238; text-align: center; }"
                         "h1 { color: #113f2f !important; font-weight: 800; }"
                         ".card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.05); margin: 20px auto; text-align: left; max-width: 400px; }"
                         ".btn-dash { display: block; text-align: center; background: #113f2f; color: white; padding: 12px; border-radius: 8px; text-decoration: none; font-weight: bold; margin-top: 15px; }"
                         "</style>"
                         "<div class='card'>"
                         "<h3>🌿 Leir 에코시스템 가이드</h3>"
                         "<p style='font-size:13px; color:#546e7a; line-height:1.5; margin-top:6px;'>"
                         "본 노드 장치는 사물인터넷 기술을 기반으로 주변 토양 환경을 정밀 계측하는 <b>Leir 디바이스</b>입니다. "
                         "아래 버튼을 눌러 현재 장소의 와이파이를 지정해 주시면 실시간 관제가 가동됩니다."
                         "</p>"
                         "<a class='btn-dash' href='" + String(leir_dashboard_url) + "' target='_blank'>🖥️ Leir 통합 관제 화면 열기</a>"
                         "</div>";
                         
    wm.setCustomHeadElement(custom_html.c_str());
    
    // 넌블로킹 설정 (설정창이 열려도 loop의 블루투스/센서가 멈추지 않음)
    wm.setConfigPortalBlocking(false); 
    wm.setConfigPortalTimeout(180); 

    String apName = "Leir_Setup_" + student_id;
    wm.autoConnect(apName.c_str()); 
    
    mqttClient.setServer(mqtt_server, 1883);
}

void loop() {
    // ⭐ [수정 핵심] 전역 변수로 선언된 wm을 그대로 사용하여 백그라운드 웹 서버를 중단 없이 처리합니다.
    wm.process(); 

    if (WiFi.status() == WL_CONNECTED) {
        isWifiConnected = true;
        if (!mqttClient.connected()) {
            String clientId = "LeirC3-Client-" + student_id;
            if (mqttClient.connect(clientId.c_str())) {
                Serial.println("☁️ Leir 클라우드 브로커 동기화 완료.");
            }
        }
        mqttClient.loop();
    } else {
        isWifiConnected = false;
    }

    unsigned long currentMillis = millis();
    if (currentMillis - lastMsgTime >= msgInterval) {
        lastMsgTime = currentMillis;

        int soilRaw = analogRead(SOIL_PIN);
        int soilPercent = map(soilRaw, 4096, 0, 0, 100);
        soilPercent = constrain(soilPercent, 0, 100);

        sensors.requestTemperatures();
        float tempC = sensors.getTempCByIndex(0);

        String jsonPayload = "{\"id\":\"" + student_id + 
                             "\",\"moisture\":" + String(soilPercent) + 
                             ",\"temperature\":" + String(tempC) + "}";
        
        if (isWifiConnected && mqttClient.connected()) {
            mqttClient.publish(mqtt_topic, jsonPayload.c_str());
        }
        
        if (deviceConnected) {
            pTxCharacteristic->setValue(jsonPayload.c_str());
            pTxCharacteristic->notify();
        }
        Serial.println("📊 송신 패킷 ➔ " + jsonPayload);
    }

    if (!deviceConnected && oldDeviceConnected) {
        delay(500); 
        pServer->startAdvertising(); 
        oldDeviceConnected = deviceConnected;
    }
    if (deviceConnected && !oldDeviceConnected) {
        oldDeviceConnected = deviceConnected;
    }
}