#include "w5500.h"

#include <DHCP/dhcp.h>
#include <DNS/dns.h>
#include <MQTT/MQTTClient.h>
#include <W5500/w5500.h>
#include <socket.h>
#include <string.h>

#include "ch32v003fun.h"
#include "config.h"
#include "debug.h"
#include "spi_dma.h"

volatile uint32_t count = 0;
volatile int ip_assigned = 0;

// Global buffers for DHCP and DNS
static uint8_t dhcp_buffer[512];
static uint8_t dns_buffer[512];

// Function to handle IP assignment details
void handle_ip_assigned(void) {
  DEBUG_PRINT("IP Assigned!\n");

  wiz_NetInfo net_info;
  getIPfromDHCP(net_info.ip);
  getGWfromDHCP(net_info.gw);
  getSNfromDHCP(net_info.sn);

  uint8_t dns[4];
  getDNSfromDHCP(dns);

  DEBUG_PRINT(
      "IP:  %d.%d.%d.%d\nGW:  %d.%d.%d.%d\nNet: %d.%d.%d.%d\nDNS: "
      "%d.%d.%d.%d\n",
      net_info.ip[0], net_info.ip[1], net_info.ip[2], net_info.ip[3],
      net_info.gw[0], net_info.gw[1], net_info.gw[2], net_info.gw[3],
      net_info.sn[0], net_info.sn[1], net_info.sn[2], net_info.sn[3], dns[0],
      dns[1], dns[2], dns[3]);

  wizchip_setnetinfo(&net_info);
}

// Callback functions
void callback_ip_assigned(void) {
  DEBUG_PRINT("Callback: IP assigned! Leased time: %lu sec\n",
              getDHCPLeasetime());
  ip_assigned = 1;
}

void callback_ip_conflict(void) { DEBUG_PRINT("Callback: IP conflict!\n"); }

void configure_network(void) {
  DEBUG_PRINT("===\n");
  DEBUG_PRINT("Starting network configuration...\n");
  DEBUG_PRINT("===\n");

  // Setup chip select and SPI callbacks
  reg_wizchip_cs_cbfunc(spi_select, spi_unselect);
  reg_wizchip_spi_cbfunc(spi_read_byte, spi_write_byte);
  // reg_wizchip_spiburst_cbfunc(spidma_read_buffer, spidma_write_buffer);

  uint8_t rx_tx_buff_sizes[] = {2, 2, 2, 2, 2, 2, 2, 2};
  wizchip_init(rx_tx_buff_sizes, rx_tx_buff_sizes);
}

void configure_dhcp(void) {
  wiz_NetInfo net_info = {.mac = {0xEA, 0x11, 0x22, 0x33, 0x44, 0xEA},
                          .dhcp = NETINFO_DHCP};
  setSHAR(net_info.mac);
  DHCP_init(DHCP_SOCKET, dhcp_buffer);

  // Register DHCP callbacks
  reg_dhcp_cbfunc(callback_ip_assigned, callback_ip_assigned,
                  callback_ip_conflict);

  // Attempt to acquire an IP address using DHCP
  while (!ip_assigned) {
    DHCP_run();
    Delay_Ms(100);
    DEBUG_PRINT("DHCP_run()...\n");
  }

  if (!ip_assigned) {
    DEBUG_PRINT("\r\nIP was not assigned\r\n");
    return;
  }

  handle_ip_assigned();
}

// todo: rm
void resolve_domain_name(const char* domain_name) {
  DEBUG_PRINT("Resolving domain name \"%s\"...\n", domain_name);

  DNS_init(DNS_SOCKET, dns_buffer);
  // cloudflare dns
  uint8_t dns[] = {1, 1, 1, 1};
  uint8_t addr[4];
  int8_t res;
  uint8_t retries = 0;

  while (retries < 3) {
    Delay_Ms(250);

    res = DNS_run(dns, (uint8_t*)domain_name, addr);
    if (res == 1) {
      DEBUG_PRINT("Result: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], addr[3]);
      break;
    } else {
      DEBUG_PRINT("DNS_run() failed, res = %d. Retries: %u\n", res, retries);
    }
    retries++;
  }
}

MQTTClient setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts) {
  static unsigned char tx_buffer[MQTT_TX_BUFFER_SIZE];
  static unsigned char rx_buffer[MQTT_RX_BUFFER_SIZE];
  MQTTClient client;
  int rc;
  uint8_t target_ip[] = MQTT_TARGET_IP;

  NewNetwork(network, TCP_SOCKET);
  if (ConnectNetwork(network, target_ip, MQTT_TARGET_PORT) != SOCK_OK) {
    DEBUG_PRINT("Network connection failed.\n");
    return (MQTTClient){0};  // Return an empty client on failure
  }

  // Initialize the MQTT client
  MQTTClientInit(&client, network, MQTT_COMMAND_TIMEOUT_MS, tx_buffer,
                 sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer));

  // Setup MQTT connection data
  MQTTPacket_connectData connect_data = MQTTPacket_connectData_initializer;
  connect_data.willFlag = 0;
  connect_data.MQTTVersion = 3;
  connect_data.clientID.cstring = opts->clientid;
  connect_data.username.cstring = opts->username;
  connect_data.password.cstring = opts->password;
  connect_data.keepAliveInterval = MQTT_KEEP_ALIVE_INTERVAL;
  connect_data.cleansession = 1;

  // Connect to MQTT broker
  rc = MQTTConnect(&client, &connect_data);
  if (rc != 0) {
    DEBUG_PRINT("Failed to connect: %d\n", rc);
    return (MQTTClient){0};  // Return an empty client on failure
  }

  return client;
}

int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos,
                       messageHandler message_callback) {
  int rc = MQTTSubscribe(client, topic, qos, message_callback);
  if (rc != 0) {
    DEBUG_PRINT("Failed to subscribe to %s: %d\n", topic, rc);
    return rc;
  }
  return 0;  // Success
}

void publish_message(MQTTClient* client, const char* payload,
                     const char* topic) {
  MQTTMessage message = {.qos = QOS0,
                         .retained = 0,
                         .dup = 0,
                         .payload = (void*)payload,
                         .payloadlen = strlen(payload)

  };

  if (MQTTPublish(client, topic, &message) != 0) {
    DEBUG_PRINT("Publish failed\n");
  } else {
    DEBUG_PRINT("Message published successfully\n");
  }
}