package rocks.paperwork.android.sync.api;

import android.accounts.AuthenticatorException;
import android.content.Context;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import rocks.paperwork.android.adapters.NotesAdapter.Note;
import rocks.paperwork.android.adapters.Tag;
import rocks.paperwork.android.data.DatabaseContract;
import rocks.paperwork.android.data.DatabaseHelper;
import rocks.paperwork.android.data.NoteDataSource;

/**
 * Syncs notes with the server and parses note json
 */
public class NoteSync
{
    private static final String LOG_TAG = NoteSync.class.getSimpleName();

    /**
     * Creates a new note on the server
     */
    private static Note createNote(String host, String hash, Note note) throws IOException, JSONException, AuthenticatorException
    {
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;
        String jsonStr;
        String path = host + "/api/v1/notebooks/" + note.getNotebookId() + "/notes";

        try
        {
            URL url = new URL(path);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            urlConnection.setRequestProperty("Accept", "application/json");
            urlConnection.setRequestProperty("Authorization", "Basic " + hash);
            urlConnection.setConnectTimeout(10000);
            urlConnection.setReadTimeout(15000);
            urlConnection.setRequestMethod("POST");
            urlConnection.connect();

            // create json note
            JSONObject jsonNote = new JSONObject();
            jsonNote.put("title", note.getTitle());
            jsonNote.put("content", note.getContent());
            jsonNote.put("content_preview", note.getPreview());

            OutputStream outputStream = urlConnection.getOutputStream();
            outputStream.write(jsonNote.toString().getBytes());
            outputStream.flush();
            outputStream.close();

            // read response
            InputStream inputStream = urlConnection.getInputStream();
            StringBuilder builder = new StringBuilder();
            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null)
            {
                builder.append(line).append("\n");
            }
            jsonStr = builder.toString();


            int responseCode = urlConnection.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED)
            {
                throw new AuthenticatorException("Authentication failed");
            }
            else if (responseCode != HttpURLConnection.HTTP_OK)
            {
                Log.d(LOG_TAG, "Error while creating note, response code: " + urlConnection.getResponseCode());
                throw new ConnectException();
            }
            else
            {
                JSONObject json = new JSONObject(jsonStr);

                if (!json.getBoolean("success"))
                {
                    throw new ConnectException();
                }

                JSONObject jsonResponse = json.getJSONObject("response");
                return parseNote(jsonResponse.toString());
            }
        }
        finally
        {
            if (urlConnection != null)
            {
                urlConnection.disconnect();
            }

            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (final IOException e)
                {
                    Log.e(LOG_TAG, "Error closing stream", e);
                }
            }
        }
    }

    /**
     * Updates a note on the server
     */
    private static boolean updateNote(String host, String hash, Note note) throws IOException, JSONException, AuthenticatorException
    {
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;
        String jsonStr;
        String path = host + "/api/v1/notebooks/" + note.getNotebookId() + "/notes/" + note.getId();

        try
        {
            URL url = new URL(path);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            urlConnection.setRequestProperty("Accept", "application/json");
            urlConnection.setRequestProperty("Authorization", "Basic " + hash);
            urlConnection.setConnectTimeout(10000);
            urlConnection.setReadTimeout(15000);
            urlConnection.setRequestMethod("PUT");
            urlConnection.connect();

            // create json note
            JSONObject jsonNote = new JSONObject();
            jsonNote.put("title", note.getTitle());
            jsonNote.put("content", note.getContent());

            OutputStream outputStream = urlConnection.getOutputStream();
            outputStream.write(jsonNote.toString().getBytes());
            outputStream.flush();
            outputStream.close();

            // read response
            InputStream inputStream = urlConnection.getInputStream();
            StringBuilder builder = new StringBuilder();
            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null)
            {
                builder.append(line).append("\n");
            }
            jsonStr = builder.toString();


            int responseCode = urlConnection.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED)
            {
                throw new AuthenticatorException("Authentication failed");
            }
            else if (responseCode != HttpURLConnection.HTTP_OK)
            {
                Log.d(LOG_TAG, "Error while creating note, response code: " + urlConnection.getResponseCode());
                throw new ConnectException();
            }
            else
            {
                JSONObject json = new JSONObject(jsonStr);

                if (json.getBoolean("success"))
                {
                    return true;
                }
                else
                {
                    throw new ConnectException();
                }
            }
        }
        finally
        {
            if (urlConnection != null)
            {
                urlConnection.disconnect();
            }

            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (final IOException e)
                {
                    Log.e(LOG_TAG, "Error closing stream", e);
                }
            }
        }
    }

    /**
     * Moves a note on the server to a different notebook
     */
    private static void moveNote(String host, String hash, Note note) throws IOException, JSONException, AuthenticatorException
    {
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;
        String jsonStr;
        String path = host + "/api/v1/notebooks/" + note.getOldNotebookId() + "/notes/" + note.getId() + "/move/" + note.getNotebookId();

        try
        {
            URL url = new URL(path);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            urlConnection.setRequestProperty("Accept", "application/json");
            urlConnection.setRequestProperty("Authorization", "Basic " + hash);
            urlConnection.setConnectTimeout(10000);
            urlConnection.setReadTimeout(15000);
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();

            // read response
            InputStream inputStream = urlConnection.getInputStream();
            StringBuilder builder = new StringBuilder();
            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null)
            {
                builder.append(line).append("\n");
            }
            jsonStr = builder.toString();


            int responseCode = urlConnection.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED)
            {
                throw new AuthenticatorException("Authentication failed");
            }
            else if (responseCode != HttpURLConnection.HTTP_OK)
            {
                Log.d(LOG_TAG, "Error while creating note, response code: " + urlConnection.getResponseCode());
                throw new ConnectException();
            }
            else
            {
                JSONObject json = new JSONObject(jsonStr);

                if (!json.getBoolean("success"))
                {
                    throw new ConnectException();
                }
            }
        }
        finally
        {
            if (urlConnection != null)
            {
                urlConnection.disconnect();
            }

            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (final IOException e)
                {
                    Log.e(LOG_TAG, "Error closing stream", e);
                }
            }
        }
    }

    private static void deleteNote(String host, String hash, Note note) throws IOException, JSONException, AuthenticatorException
    {
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;
        String jsonStr;
        String path = host + "/api/v1/notebooks/" + note.getNotebookId() + "/notes/" + note.getId();

        try
        {
            URL url = new URL(path);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            urlConnection.setRequestProperty("Accept", "application/json");
            urlConnection.setRequestProperty("Authorization", "Basic " + hash);
            urlConnection.setConnectTimeout(10000);
            urlConnection.setReadTimeout(15000);
            urlConnection.setRequestMethod("DELETE");
            urlConnection.connect();

            // create json note
            JSONObject jsonNote = new JSONObject();
            jsonNote.put("title", note.getTitle());
            jsonNote.put("content", note.getContent());

            OutputStream outputStream = urlConnection.getOutputStream();
            outputStream.write(jsonNote.toString().getBytes());
            outputStream.flush();
            outputStream.close();

            // read response
            InputStream inputStream = urlConnection.getInputStream();
            StringBuilder builder = new StringBuilder();
            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null)
            {
                builder.append(line).append("\n");
            }
            jsonStr = builder.toString();


            int responseCode = urlConnection.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED)
            {
                throw new AuthenticatorException("Authentication failed");
            }
            else if (responseCode != HttpURLConnection.HTTP_OK)
            {
                Log.d(LOG_TAG, "Error while creating note, response code: " + urlConnection.getResponseCode());
                throw new ConnectException();
            }
            else
            {
                JSONObject json = new JSONObject(jsonStr);

                if (!json.getBoolean("success"))
                {
                    throw new ConnectException();
                }
            }
        }
        finally
        {
            if (urlConnection != null)
            {
                urlConnection.disconnect();
            }

            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (final IOException e)
                {
                    Log.e(LOG_TAG, "Error closing stream", e);
                }
            }
        }
    }

    /**
     * Uploads all new local notes
     */
    public static void uploadNotes(Context context, String host, String hash) throws IOException, JSONException, AuthenticatorException
    {
        NoteDataSource dataSource = NoteDataSource.getInstance(context);
        List<Note> notSyncedNotes = dataSource.getNotes(DatabaseContract.NoteEntry.NOTE_STATUS.not_synced);

        for (Note note : notSyncedNotes)
        {
            Note newNote = createNote(host, hash, note);
            if (newNote != null)
            {
                dataSource.deleteNote(note);
                dataSource.insertNote(newNote);
            }
            else
            {
                Log.d(LOG_TAG, "Creating note failed");
            }
        }
    }

    /**
     * Updates notes on the server
     *
     * @param updatedNotes Notes that should be updated on the server
     */
    public static void updateNotes(Context context, String host, String hash, List<Note> updatedNotes) throws IOException, JSONException, AuthenticatorException
    {
        for (Note localNote : updatedNotes)
        {
            if (updateNote(host, hash, localNote))
            {
                localNote.setSyncStatus(DatabaseContract.NoteEntry.NOTE_STATUS.synced);
                NoteDataSource.getInstance(context).updateNote(localNote);
            }
            else
            {
                Log.d(LOG_TAG, "Updating note failed");
            }
        }
    }

    /**
     * Updates notes on the server
     *
     * @param movedNotes Notes that should be moved to a different notebook on the server
     */

    public static void moveNotes(Context context, String host, String hash, List<Note> movedNotes) throws IOException, JSONException, AuthenticatorException
    {
        for (Note localNote : movedNotes)
        {
            moveNote(host, hash, localNote);
            localNote.setSyncStatus(DatabaseContract.NoteEntry.NOTE_STATUS.synced);
            NoteDataSource.getInstance(context).updateNote(localNote);
        }
    }

    /**
     * Delete all local deleted notes on the server
     */
    public static void deleteNotes(Context context, String host, String hash) throws IOException, JSONException, AuthenticatorException
    {
        NoteDataSource dataSource = NoteDataSource.getInstance(context);
        List<Note> deletedNotes = dataSource.getNotes(DatabaseContract.NoteEntry.NOTE_STATUS.deleted);

        for (Note note : deletedNotes)
        {
            deleteNote(host, hash, note);
            dataSource.deleteNote(note);
        }
    }

    private static Note parseNote(String jsonStr) throws JSONException
    {
        JSONObject jsonNote = new JSONObject(jsonStr);
        JSONObject version = jsonNote.getJSONObject("version");

        String id = jsonNote.getString("id");
        String title = version.getString("title");
        String content = version.getString("content");
        Date date = DatabaseHelper.getDateTime(jsonNote.getString("updated_at"));
        String notebookId = jsonNote.getString("notebook_id");

        List<Tag> tags = new ArrayList<>();

        JSONArray jsonTags = jsonNote.getJSONArray("tags");

        for (int i = 0; i < jsonTags.length(); i++)
        {
            JSONObject jsonTag = jsonTags.getJSONObject(i);
            String tagId = jsonTag.getString("id");
            String tagTitle = jsonTag.getString("title");

            Tag tag = new Tag(tagId);
            tag.setTitle(tagTitle);

            tags.add(tag);
        }

        Note note = new Note(id);
        note.setNotebookId(notebookId);
        note.setTitle(title);
        note.setContent(content);
        note.setUpdatedAt(date);
        note.setSyncStatus(DatabaseContract.NoteEntry.NOTE_STATUS.synced);
        note.setTags(tags);

        return note;
    }

    public static List<Note> parseNotes(String jsonStr) throws JSONException
    {
        List<Note> notes = new ArrayList<>();

        JSONObject jsonData;
        jsonData = new JSONObject(jsonStr);

        JSONArray jsonNotes = jsonData.getJSONArray("response");

        for (int i = 0; i < jsonNotes.length(); i++)
        {
            JSONObject jsonNote = jsonNotes.getJSONObject(i);
            Note note = parseNote(jsonNote.toString());

            notes.add(note);
        }

        return notes;
    }
}