#include <stdio.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_app_desc.h"

#include "dev_name.h"
#include "storage.h"
#include "system.h"
#include "crypto.h"
#include "unsecure_api.h"
#include "secure_api.h"
#include "ethernet.h"
#include "constants.h"
#include "relays.h"
#include "ota.h"

static const char *TAG = "main";

void app_main(void)
{
    esp_log_level_set("*", ESP_LOG_INFO);

    system_show_free_memory();

    ESP_LOGI(TAG, "SolarEnergy WT32-ETH01 device version %s", esp_app_get_description()->version);

    // Turn off all relays
    relays_turn_off_all();
    relays_setup();

    // Initialize storage
    if (storage_init() == false)
    {
        ESP_LOGE(TAG, "Failed to init storage!");
        reboot();
    }

    // Give a name to the device
    if (dev_generate_name())
    {
        ESP_LOGI(TAG, "Generated a new device name");
    }

    char *name = dev_name();
    ESP_LOGI(TAG, "Dev name: %s", name);
    free(name);

    // Generate private key, if needed
    if (crypto_gen_priv_key())
    {
        ESP_LOGI(TAG, "Generated device private key!");
    }
    ESP_LOGI(TAG, "Device private key:");
    crypto_print_priv_key();

    // Show current private key
    char *csr = crypto_get_csr();
    ESP_LOGI(TAG, "Current CSR:\n%s", csr);
    free(csr);

    // Initialize network stack
    ESP_LOGI(TAG, "Initialize network");
    ethernet_init();
    ethernet_wait_for_network();

    // Get if secure origin endpoint is known
    ESP_LOGI(TAG, "Check secure origin");
    if (storage_get_secure_origin(NULL) == 0)
    {
        char *sec_ori = unsecure_api_get_secure_origin();
        if (!sec_ori)
        {
            ESP_LOGE(TAG, "Failed to fetch secure origin!");
            reboot();
        }
        storage_set_secure_origin(sec_ori);
        free(sec_ori);
    }

    // Print secure origin endpoint for debugging purposes
    ESP_LOGI(TAG, "Get secure origin");
    char *sec_ori = calloc(SEC_ORIG_LEN, 1);
    assert(storage_get_secure_origin(sec_ori) > 0);
    ESP_LOGI(TAG, "Current secure origin: %s", sec_ori);
    free(sec_ori);

    // Check if root CA is available locally
    ESP_LOGI(TAG, "Check root CA");
    if (storage_get_root_ca(NULL) == 0)
    {
        char *root_ca = unsecure_api_get_root_ca();
        if (!root_ca)
        {
            ESP_LOGE(TAG, "Failed to fetch root CA!");
            reboot();
        }
        storage_set_root_ca(root_ca);
        free(root_ca);
    }

    // Print root CA for debugging purposes
    ESP_LOGI(TAG, "Get root CA");
    char *root_ca = calloc(ROOT_CA_MAX_BYTES, 1);
    assert(storage_get_root_ca(root_ca) > 0);
    ESP_LOGI(TAG, "Current root CA:\n%s", root_ca);
    free(root_ca);

    bool validated = false;
    while (!validated)
    {
        // Check current device enrollment status
        ESP_LOGI(TAG, "Check enrollment status");
        enum DevEnrollmentStatus status = secure_api_get_device_enrollment_status();
        ESP_LOGI(TAG, "Current enrollment status: %d", status);

        switch (status)
        {
        case DevEnrollError:
            ESP_LOGE(TAG, "Failed to retrieve device enrollment status!");
            break;

        case DevEnrollPending:
            ESP_LOGI(TAG, "Device enrolled, but not validated yet. Please accept device on central system web UI");
            break;

        case DevEnrollValidated:
            ESP_LOGI(TAG, "Device enrolled and validated. Ready to operate!");
            validated = true;
            break;

        case DevEnrollUnknown:
            ESP_LOGI(TAG, "Device unknown, need to enroll!");

            // Remove certificate if present
            storage_set_dev_cert("");

            // Enroll device
            ESP_LOGI(TAG, "Enroll device");
            if (secure_api_enroll_device() != 0)
            {
                ESP_LOGE(TAG, "Failed to enroll device!");
                reboot();
            }
            ESP_LOGI(TAG, "Requested device enrollment.");
            break;
        }

        // Wait before next try
        if (!validated)
            system_sleep(60);
    };

    // Retrieve device certificate if missing
    ESP_LOGI(TAG, "Check device certificate");
    if (storage_get_dev_cert(NULL) < 2)
    {
        char *dev_cert = secure_api_get_dev_certificate();
        if (!dev_cert)
        {
            ESP_LOGE(TAG, "Failed to fetch device certificate!");
            reboot();
        }
        storage_set_dev_cert(dev_cert);
        free(dev_cert);
    }

    // Print device certificate for debugging purposes
    ESP_LOGI(TAG, "Get device certificate");
    char *dev_certificate = calloc(DEV_CERT_MAX_BYTES, 1);
    assert(storage_get_dev_cert(dev_certificate) > 0);
    ESP_LOGI(TAG, "Current device certificate:\n%s", dev_certificate);
    free(dev_certificate);

    // Main loop
    ESP_LOGI(TAG, "Starting main loop");
    secure_api_report_log_message(Info, "Starting program main loop");

    size_t fails = 0;
    while (true)
    {
        sync_response *res = secure_api_sync_device();
        if (!res)
        {
            fails += 1;
            ESP_LOGE(TAG, "Failed to synchronise device! (number=%d)", fails);

            // Safely turn off all relays after a given number of failures
            if (fails > 5)
            {
                ESP_LOGE(TAG, "Many failures, will stop all relays...");
                relays_turn_off_all();
            }

            // Restart the card after too much failures
            if (fails > 10)
            {
                ESP_LOGE(TAG, "Too many failures, will try to reboot in 3 secs...");
                system_sleep(3);
                reboot();
            }

            system_sleep(SYNC_TIME_INTERVAL);
            continue;
        }

        fails = 0;

        sync_response_print(res);

        // Check for firmware update
        if (res->available_update)
        {
            ESP_LOGI(TAG, "Will perform system upgrade to version %s!", res->available_update);
            relays_turn_off_all();

            secure_api_report_log_message(Info, "Device is starting the OTA procedure...");

            if (ota_perform_update(res->available_update))
            {
                ESP_LOGI(TAG, "OTA update succesfully executed, will reboot...");
                secure_api_report_log_message(Info, "Device successfully updated!");
            }
            else
            {
                ESP_LOGE(TAG, "OTA update failed! Will reboot...");
                secure_api_report_log_message(Error, "Device update failed!");
            }

            secure_api_report_log_message(Info, "Device will restart after OTA procedure...");

            system_sleep(SYNC_TIME_INTERVAL);
            reboot();
        }

        // Update relays configuration
        for (size_t i = 0; i < relays_count(); i++)
        {
            relays_set(i, sync_response_is_relay_on(res, i));
        }

        sync_response_free(res);
        system_sleep(SYNC_TIME_INTERVAL);
    }

    reboot();
}