package ch.hevs.isi.db;


import ch.hevs.isi.MinecraftController;
import ch.hevs.isi.core.DataPoint;
import ch.hevs.isi.core.DataPointListener;
import ch.hevs.isi.core.FloatDataPoint;
import ch.hevs.isi.utils.Utility;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;


public class DatabaseConnector implements DataPointListener {
    private final Properties properties = new Properties();     // Properties of the config.properties file
    private URL urlForWrite = null;                             // URL of the InfluxDB server
    private HttpURLConnection con = null;                       // Connection to the InfluxDB server
    private boolean initialized = false;                        // Boolean to know if the database connector is initialized
    private final TimeManager _timeManager = new TimeManager(3); // Time manager to manage the time of the data points
    private long _timestamp = 0;                                // Timestamp of the data points
    public static String token = System.getenv("SECRET_TOKEN"); // Token to access the InfluxDB server
    public static String url = null;                            // URL of the InfluxDB server
    public static String org = null;                            // Organization of the InfluxDB server
    public static String bucket = null;                         // Bucket of the InfluxDB server


    private static DatabaseConnector  mySelf = null;

    /**
     * Private constructor of the database connector
     * Read the config.properties file and initialize the database connector
     */
    private DatabaseConnector (){
        // Read the config.properties file
        /*
        try (InputStream input = Files.newInputStream(Paths.get("config.properties"))) {
            properties.load(input);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Get the URL, the organization and the bucket from the config.properties file if their are null
        if (url == null){
            url = properties.getProperty("DB.URL");
        }
        if (org == null){
            org = properties.getProperty("DB.ORG");
        }
        if (bucket == null){
            bucket = properties.getProperty("DB.BUCKET");
        }
        */

        // Subscribe to the update of DataPoints
        DataPointListener.subscribeUpdate(this);
    }

    /**
     * Get the instance of the database connector
     * @return The instance of the database connector
     */
    public static DatabaseConnector  getMySelf(){
        if (mySelf == null){
            mySelf = new DatabaseConnector();
        }
        return mySelf;
    }

    /**
     * Initialize the database connector
     * @param url URL of the database. If null take the URL from the config.properties file
     */
    public void initialize(String url){
        // Full URL of the InfluxDB server
        String fullURL;
        try{
            if(urlForWrite == null){
                /*
                if(url == null){
                    url = properties.getProperty("DB.URL");
                }
                */
                fullURL = url + "/api/v2/write?org=" + org + "&bucket=" + bucket;
                Utility.pDebug("URL: " + fullURL);
                urlForWrite = new URL(fullURL);
            }
            initialized = true;
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        // If the user want to erase the previous data in the database
        // Delete the previous data
        if(MinecraftController.ERASE_PREVIOUS_DATA_INB_DB){
            try {
                // Create the URL to delete the data
                fullURL = url + "/api/v2/delete?org=" + org + "&bucket=" + bucket;
                Utility.pDebug("URL: " + fullURL);
                URL urlForDelete = new URL(fullURL);
                // Create the connection to the database
                con = (HttpURLConnection) urlForDelete.openConnection();
                con.setRequestMethod("POST");
                con.setRequestProperty("Authorization", "Token " + token);
                con.setRequestProperty("Content-Type", "text/plain");
                con.setRequestProperty("Accept", "application/json");
                con.setDoOutput(true);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            // Create the data to send to delete the data in the database and send it
            String data = "{\n";
            data += "\"start\": \"2000-01-01T00:00:00Z\",\n";
            data += "\"stop\": \"2024-04-25T10:45:00Z\",\n";
            data += "\"predicate\": \"_measurement=\\\"Minecraft\\\"\"\n";
            data += "}";
            Utility.pDebug(data);
            sendDataToDatabase(data);
        }
    }

    /**
     * Add the timestamp to the data point
     * @param dp Data point to add the timestamp
     */
    private void pushToDatabase(DataPoint dp) {
        // Initialize the database connector if not already done
        if(!initialized){
            initialize(null);
        }

        // Create the data to send
        String data = "Minecraft ";
        data += dp.toString();
        data += " " + _timestamp;

        // Send the data to the database
        sendDataToDatabase(data);
    }

    /**
     * Create and formate the request to send to the database
     * Send the request to the database
     * Check if the data is correctly send to the database with the response code
     * @param data Data to send to the database
     */
    private void sendDataToDatabase(String data){
        try {
            // Create connection and set headers
            if(con == null){
                con = (HttpURLConnection) urlForWrite.openConnection();
                con.setRequestMethod("POST");
                con.setRequestProperty("Authorization", "Token " + token);
                con.setRequestProperty("Content-Type", "application/json");
                con.setDoOutput(true);
            }

            // Send data
            OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream());
            writer.write(data);
            writer.flush();

            // Get response code
            int respondCode = con.getResponseCode();
            if(respondCode != 204){
                System.out.println("Error: " + respondCode + ", Data: " + data);
            } else {
                Utility.pDebug(data + " -> Database");
            }
            con.disconnect();
            con = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Push the data point to the database
     * @param dp Data point to push
     */
    @Override
    public void onNewValue(DataPoint dp) {
        if(dp.getLabel().equals("CLOCK_FLOAT")) {
            FloatDataPoint fdp = (FloatDataPoint) dp;
            _timeManager.setTimestamp(fdp.getValue());
            _timestamp = _timeManager.getNanosForDB();
        }
        if(_timestamp != 0){
            pushToDatabase(dp);
        }
    }
}
