package jp.juggler.subwaytooter.api;

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;

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

import java.io.IOException;
import java.util.UUID;

import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.CancelChecker;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.table.ClientInfo;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class TootApiClient {
	private static final LogCategory log = new LogCategory( "TootApiClient" );
	
	static final OkHttpClient ok_http_client = App1.ok_http_client;
	
	public interface Callback {
		boolean isApiCancelled();
		
		void publishApiProgress( String s );
	}
	
	private final Context context;
	public final Callback callback;
	
	public TootApiClient( @NonNull Context context, @NonNull Callback callback ){
		this.context = context;
		this.callback = callback;
	}
	
	private String instance;
	private String user_mail;
	private String password;
	
	private SavedAccount account;
	
	public void setUserInfo( String instance, String user_mail, String password ){
		this.instance = instance;
		this.user_mail = user_mail;
		this.password = password;
	}
	
	public void setAccount( SavedAccount account ){
		this.instance = account.host;
		this.account = account;
	}
	
	public static final MediaType MEDIA_TYPE_FORM_URL_ENCODED = MediaType.parse( "application/x-www-form-urlencoded" );
	
	public TootApiResult request( String path ){
		return request(path,new Request.Builder() );
	}

	public TootApiResult request( String path, Request.Builder request_builder ){
		
		JSONObject client_info = null;
		JSONObject token_info = ( account == null ? null : account.token_info );
		
		for( ; ; ){
			if( callback.isApiCancelled() ) return null;
			if( token_info == null ){
				if( client_info == null ){
					// DBにあるならそれを使う
					client_info = ClientInfo.load( instance );
					if( client_info != null ) continue;
					
					callback.publishApiProgress( context.getString( R.string.register_app_to_server, instance ) );
					
					// OAuth2 クライアント登録
					String client_name = "jp.juggler.subwaytooter." + UUID.randomUUID().toString();
					
					Request request = new Request.Builder()
						.url( "https://" + instance + "/api/v1/apps" )
						.post( RequestBody.create( MEDIA_TYPE_FORM_URL_ENCODED
							, "client_name=" + Uri.encode( client_name )
								+ "&redirect_uris=urn:ietf:wg:oauth:2.0:oob"
								+ "&scopes=read write follow"
						) )
						.build();
					Call call = ok_http_client.newCall( request );
					
					Response response;
					try{
						response = call.execute();
					}catch( Throwable ex ){
						return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
					}
					if( callback.isApiCancelled() ) return null;
					
					if( ! response.isSuccessful() ){
						return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
					}
					try{
						String json = response.body().string();
						if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
							return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
						}
						// {"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"******","client_secret":"******"}
						client_info = new JSONObject( json );
						String error = Utils.optStringX( client_info, "error" );
						if( ! TextUtils.isEmpty( error ) ){
							return new TootApiResult( context.getString( R.string.api_error, error ) );
						}
						ClientInfo.save( instance, json );
						continue;
					}catch( Throwable ex ){
						ex.printStackTrace();
						return new TootApiResult( Utils.formatError( ex, "API data error" ) );
					}
				}
				
				if( password == null ){
					// 手動でアクセストークンを再取得しなければいけない
					return new TootApiResult( context.getString( R.string.login_required ) );
				}
				
				callback.publishApiProgress( context.getString( R.string.request_access_token ) );
				
				// アクセストークンの取得
				
				Request request = new Request.Builder()
					.url( "https://" + instance + "/oauth/token" )
					.post( RequestBody.create(
						MEDIA_TYPE_FORM_URL_ENCODED
						,"client_id=" + Uri.encode( Utils.optStringX( client_info, "client_id" ) )
							+ "&client_secret=" + Uri.encode( Utils.optStringX( client_info, "client_secret" ) )
							+ "&grant_type=password"
							+ "&username=" + Uri.encode( user_mail )
							+ "&password=" + Uri.encode( password )
							+ "&scope=read write follow"
							+ "&scopes=read write follow"
					))
					.build();
				Call call = ok_http_client.newCall( request );
				
				Response response;
				try{
					response = call.execute();
				}catch( Throwable ex ){
					return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
				}
				if( callback.isApiCancelled() ) return null;
				
				// TODO: アプリIDが無効な場合はどんなエラーが出る？
				
				if( ! response.isSuccessful() ){
					return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
				}
				try{
					String json = response.body().string();
		
					// {"access_token":"******","token_type":"bearer","scope":"read","created_at":1492334641}
					if( TextUtils.isEmpty( json ) || json.charAt( 0 ) == '<' ){
						return new TootApiResult( context.getString( R.string.login_failed ) );
					}
					token_info = new JSONObject( json );
					String error = Utils.optStringX( client_info, "error" );
					if( ! TextUtils.isEmpty( error ) ){
						return new TootApiResult( context.getString( R.string.api_error, error ) );
					}
					if( account != null ){
						account.updateTokenInfo( token_info );
					}
					continue;
				}catch( Throwable ex ){
					ex.printStackTrace();
					return new TootApiResult( Utils.formatError( ex, "API data error" ) );
				}
			}
			
			// アクセストークンを使ってAPIを呼び出す
			{
				callback.publishApiProgress( context.getString( R.string.request_api, path ) );
				
				Request request  = request_builder
					.url("https://" + instance + path)
					.header( "Authorization", "Bearer " + Utils.optStringX( token_info, "access_token" ) )
					.build();
				
				Call call = ok_http_client.newCall( request );
				Response response;
				try{
					response = call.execute();
				}catch( Throwable ex ){
					return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
				}

				if( callback.isApiCancelled() ) return null;
				
				// TODO: アクセストークンが無効な場合はどうなる？
				// TODO: アプリIDが無効な場合はどうなる？
				
				if( ! response.isSuccessful() ){
					return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
				}
				
				try{
					String json = response.body().string();
		
					if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
						return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
					}else if( json.startsWith( "[" ) ){
						JSONArray array = new JSONArray( json );
						return new TootApiResult( token_info, json, array );
					}else{
						JSONObject object = new JSONObject( json );
						
						String error = Utils.optStringX( object, "error" );
						if( ! TextUtils.isEmpty( error ) ){
							return new TootApiResult( context.getString( R.string.api_error, error ) );
						}
						return new TootApiResult( token_info, json, object );
					}
				}catch( Throwable ex ){
					ex.printStackTrace();
					return new TootApiResult( Utils.formatError( ex, "API data error" ) );
				}
			}
		}
	}
}
