package org.wikipedia.useroption.dataclient;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.google.gson.annotations.SerializedName;

import org.wikipedia.WikipediaApp;
import org.wikipedia.csrf.CsrfToken;
import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.ServiceError;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.dataclient.retrofit.RetrofitFactory;
import org.wikipedia.useroption.UserOption;
import org.wikipedia.util.log.L;

import java.io.IOException;

import retrofit2.Call;
import retrofit2.Response;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

public class DefaultUserOptionDataClient implements UserOptionDataClient {
    @NonNull private final WikiSite wiki;
    @NonNull private final Client client;

    public DefaultUserOptionDataClient(@NonNull WikiSite wiki) {
        this.wiki = wiki;
        client = RetrofitFactory.newInstance(wiki).create(Client.class);
    }

    @NonNull
    @Override
    public UserInfo get() throws IOException {
        Response<MwQueryResponse<QueryUserInfo>> rsp = client.get().execute();
        if (rsp.isSuccessful() && rsp.body().success()) {
            //noinspection ConstantConditions
            return rsp.body().query().userInfo();
        }
        ServiceError err = rsp.body() == null || rsp.body().getError() == null
                ? null
                : rsp.body().getError();
        throw new IOException(err == null ? rsp.message() : err.getDetails());
    }

    @Override
    public void post(@NonNull UserOption option) throws IOException {
        Response<PostResponse> rsp = client.post(getToken(), option.key(), option.val()).execute();
        if (rsp.isSuccessful()) {
            rsp.body().check(wiki);
            return;
        }

        String msg = rsp.body() == null || rsp.body().info() == null
                ? rsp.message()
                : rsp.body().info();
        throw new IOException(msg);
    }

    @Override
    public void delete(@NonNull String key) throws IOException {
        client.delete(getToken(), key).execute().body().check(wiki);
    }

    @NonNull private String getToken() throws IOException {
        if (app().getCsrfTokenStorage().token(wiki) == null) {
            requestToken();
        }

        String token = app().getCsrfTokenStorage().token(wiki);
        if (token == null) {
            throw new IOException("No token for " + wiki.authority());
        }
        return token;
    }

    private void requestToken() {
        new CsrfTokenClient().request(wiki, new CsrfTokenClient.Callback() {
            @Override
            public void success(@NonNull Call<CsrfToken> call, @NonNull String token) {
                app().getCsrfTokenStorage().token(wiki, token);
            }

            @Override
            public void failure(@NonNull Call<CsrfToken> call, @NonNull Throwable caught) {
                // Don't worry about it; will be retried next time.
                L.w(caught);
            }
        });
    }

    private static WikipediaApp app() {
        return WikipediaApp.getInstance();
    }

    private interface Client {
        String ACTION = "w/api.php?format=json&formatversion=2&action=";

        @GET(ACTION + "query&meta=userinfo&uiprop=options")
        @NonNull Call<MwQueryResponse<QueryUserInfo>> get();

        @FormUrlEncoded
        @POST(ACTION + "options")
        @NonNull Call<PostResponse> post(@Field("token") @NonNull String token,
                                         @Query("optionname") @NonNull String key,
                                         @Query("optionvalue") @Nullable String value);

        @FormUrlEncoded
        @POST(ACTION + "options")
        @NonNull Call<PostResponse> delete(@Field("token") @NonNull String token,
                                           @Query("change") @NonNull String key);
    }

    private static class PostResponse extends MwPostResponse {
        @SuppressWarnings("unused") private String options;

        public String result() {
            return options;
        }

        public void check(@NonNull WikiSite wiki) throws IOException {
            if (!success(options)) {
                if (badToken()) {
                    app().getCsrfTokenStorage().token(wiki, null);
                }

                throw new IOException("Bad response for wiki " + wiki.host() + " = " + result());
            }
        }
    }

    private static class QueryUserInfo {
        @SuppressWarnings("unused") @SerializedName("userinfo") private UserInfo userInfo;

        UserInfo userInfo() {
            return userInfo;
        }
    }
}

@@ -1006,21 +1006,23 @@
 nal 
-Client client
+Service service
 ;%0A%0A 
@@ -1119,22 +1119,23 @@
         
-client
+service
  = Retro
@@ -1170,22 +1170,23 @@
 .create(
-Client
+Service
 .class);
@@ -1322,22 +1322,23 @@
 %3E rsp = 
-client
+service
 .get().e
@@ -1861,22 +1861,23 @@
 %3E rsp = 
-client
+service
 .post(ge
@@ -2294,22 +2294,23 @@
         
-client
+service
 .delete(
@@ -3354,32 +3354,60 @@
 tance();%0A    %7D%0A%0A
+    // todo: rename service%0A
     private inte
@@ -3412,22 +3412,23 @@
 terface 
-Client
+Service
  %7B%0A     

rename DefaultUserOptionDataClient service interface