#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_PN532.h>

#if __has_include("l484_door_config.h")
  #include "l484_door_config.h"
#else
  #include "l484_door_config.example.h"
#endif

Adafruit_PN532 nfc(PN532_IRQ_PIN, PN532_RESET_PIN, &Wire);

static unsigned long lastReadAt = 0;
static String lastCredential = "";

String bytesToHex(const uint8_t *data, uint8_t length) {
  String out;
  out.reserve(length * 2);
  for (uint8_t i = 0; i < length; i++) {
    if (data[i] < 0x10) out += "0";
    out += String(data[i], HEX);
  }
  out.toUpperCase();
  return out;
}

String jsonEscape(const String &value) {
  String out;
  out.reserve(value.length() + 8);
  for (size_t i = 0; i < value.length(); i++) {
    char c = value[i];
    if (c == '"' || c == '\\') out += '\\';
    out += c;
  }
  return out;
}

void connectWifi() {
  if (WiFi.status() == WL_CONNECTED) return;

  Serial.printf("Connecting to Wi-Fi %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  unsigned long startedAt = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startedAt < 15000) {
    Serial.print(".");
    delay(350);
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Wi-Fi connected: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("Wi-Fi connection failed.");
  }
}

bool readCardUid(uint8_t *uid, uint8_t *uidLength) {
  return nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, uidLength, 350);
}

String readMifareSecret(uint8_t *uid, uint8_t uidLength) {
  uint8_t data[16];
  uint8_t key[6];
  memcpy(key, MIFARE_KEY_A, sizeof(key));

  bool authed = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, MIFARE_SECRET_BLOCK, 0, key);
  if (!authed) {
    Serial.println("MIFARE auth failed.");
    return "";
  }

  bool readOk = nfc.mifareclassic_ReadDataBlock(MIFARE_SECRET_BLOCK, data);
  if (!readOk) {
    Serial.println("MIFARE block read failed.");
    return "";
  }

  String secret;
  for (uint8_t i = 0; i < sizeof(data); i++) {
    if (data[i] == 0x00) break;
    if (data[i] >= 32 && data[i] <= 126) secret += static_cast<char>(data[i]);
  }
  secret.trim();
  return secret;
}

String readCredential() {
  uint8_t uid[7] = { 0 };
  uint8_t uidLength = 0;
  if (!readCardUid(uid, &uidLength)) return "";

#if CARD_MODE == CARD_MODE_MIFARE_CLASSIC_BLOCK
  String secret = readMifareSecret(uid, uidLength);
  if (secret.length() >= 6) return secret;
  Serial.println("No usable card secret found.");
  return "";
#else
  return bytesToHex(uid, uidLength);
#endif
}

bool requestAccess(const String &credential) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("No Wi-Fi; deny.");
    return false;
  }

  HTTPClient http;
  http.setTimeout(ACCESS_TIMEOUT_MS);
  http.begin(L484_ACCESS_URL);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-Controller-Token", L484_CONTROLLER_TOKEN);

  String body = "{\"doorId\":\"" + jsonEscape(L484_DOOR_ID) + "\",\"cardCredential\":\"" + jsonEscape(credential) + "\"}";
  int status = http.POST(body);
  String response = http.getString();
  http.end();

  Serial.printf("Access API status: %d\n", status);
  Serial.println(response);

  if (status < 200 || status >= 300) return false;
  return response.indexOf("\"allow\":true") >= 0;
}

void setup() {
  Serial.begin(115200);
  delay(200);

  connectWifi();

  Wire.begin(PN532_SDA_PIN, PN532_SCL_PIN);
  nfc.begin();
  uint32_t version = nfc.getFirmwareVersion();
  if (!version) {
    Serial.println("PN532 not found. Check wiring and mode switches.");
    while (true) delay(1000);
  }

  nfc.SAMConfig();
  Serial.println("PN532 ready.");
}

void loop() {
  connectWifi();

  String credential = readCredential();
  if (!credential.length()) {
    delay(120);
    return;
  }

  unsigned long now = millis();
  if (credential == lastCredential && now - lastReadAt < READ_COOLDOWN_MS) {
    delay(120);
    return;
  }

  lastCredential = credential;
  lastReadAt = now;

  Serial.print("Card credential: ");
  Serial.println(credential);

  bool allowed = requestAccess(credential);
  Serial.println(allowed ? "ACCESS ALLOWED" : "ACCESS DENIED");

  delay(READ_COOLDOWN_MS);
}
