/*
 * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
 *
 * 遵循 Apache 许可证 2.0 版（"许可证"）；
 * 除非遵守许可证，否则不得使用此文件。
 * 您可以在以下网址获取许可证副本：
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * 除非适用法律要求或书面同意，否则根据许可证分发的软件是按"原样"提供的，
 * 不附带任何明示或暗示的保证或条件。请参阅许可证，了解管理权限和限制的具体语言。
 */

package net.micode.notes.gtask.remote;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
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.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;


/**
 * Google Tasks 客户端类
 * 负责与 Google Tasks 服务进行网络通信，实现任务和任务列表的增删改查及同步
 */
public class GTaskClient {
    private static final String TAG = GTaskClient.class.getSimpleName();

    private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google Tasks 根 URL
    private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // GET 请求 URL（获取数据）
    private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // POST 请求 URL（提交操作）

    private static GTaskClient mInstance = null; // 单例实例

    private DefaultHttpClient mHttpClient; // HTTP 客户端
    private String mGetUrl; // 当前 GET 请求 URL（支持自定义域名）
    private String mPostUrl; // 当前 POST 请求 URL（支持自定义域名）
    private long mClientVersion; // Google Tasks 客户端版本号（用于协议兼容）
    private boolean mLoggedin; // 登录状态
    private long mLastLoginTime; // 最后登录时间（用于超时管理）
    private int mActionId; // 操作 ID 生成器（保证每次请求唯一）
    private Account mAccount; // 当前同步的 Google 账号
    private JSONArray mUpdateArray; // 待提交的批量更新操作列表

    /**
     * 构造函数（私有化，单例模式）
     */
    private GTaskClient() {
        mHttpClient = null;
        mGetUrl = GTASK_GET_URL;
        mPostUrl = GTASK_POST_URL;
        mClientVersion = -1; // 初始版本号无效
        mLoggedin = false; // 初始未登录
        mLastLoginTime = 0; // 初始无登录时间
        mActionId = 1; // 操作 ID 从 1 开始
        mAccount = null; // 初始无账号
        mUpdateArray = null; // 初始无待更新操作
    }

    /**
     * 获取单例实例
     * @return GTaskClient 实例
     */
    public static synchronized GTaskClient getInstance() {
        if (mInstance == null) {
            mInstance = new GTaskClient();
        }
        return mInstance;
    }

    /**
     * 登录 Google 账号并初始化 Tasks 客户端
     * @param activity 调用登录的 Activity（用于获取账号令牌）
     * @return 是否登录成功
     */
    public boolean login(Activity activity) {
        // 登录状态检查：超过 5 分钟或账号变更则重新登录
        final long interval = 1000 * 60 * 5; // 5 分钟超时时间
        if (mLastLoginTime + interval < System.currentTimeMillis()) {
            mLoggedin = false;
        }
        if (mLoggedin && !TextUtils.equals(getSyncAccount().name, 
                NotesPreferenceActivity.getSyncAccountName(activity))) {
            mLoggedin = false;
        }

        if (mLoggedin) {
            Log.d(TAG, "already logged in");
            return true; // 已登录且账号有效，直接返回
        }

        mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间
        String authToken = loginGoogleAccount(activity, false); // 获取 Google 账号令牌
        if (authToken == null) {
            Log.e(TAG, "login google account failed");
            return false;
        }

        // 处理自定义域名账号（非 @gmail.com 或 @googlemail.com）
        if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || 
              mAccount.name.toLowerCase().endsWith("googlemail.com"))) {
            StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
            int index = mAccount.name.indexOf('@') + 1;
            String suffix = mAccount.name.substring(index);
            url.append(suffix + "/");
            mGetUrl = url.toString() + "ig"; // 拼接自定义 GET URL
            mPostUrl = url.toString() + "r/ig"; // 拼接自定义 POST URL

            if (tryToLoginGtask(activity, authToken)) { // 尝试登录 Tasks
                mLoggedin = true;
            }
        }

        // 处理官方域名账号（默认情况）
        if (!mLoggedin) {
            mGetUrl = GTASK_GET_URL;
            mPostUrl = GTASK_POST_URL;
            if (!tryToLoginGtask(activity, authToken)) { // 再次尝试登录
                return false;
            }
        }

        mLoggedin = true; // 登录成功
        return true;
    }

    /**
     * 登录 Google 账号获取认证令牌
     * @param activity Activity 上下文
     * @param invalidateToken 是否失效现有令牌（用于重试场景）
     * @return 认证令牌，失败返回 null
     */
    private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
        String authToken;
        AccountManager accountManager = AccountManager.get(activity);
        Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有 Google 账号

        if (accounts.length == 0) {
            Log.e(TAG, "there is no available google account");
            return null;
        }

        // 获取设置中配置的同步账号
        String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
        Account account = null;
        for (Account a : accounts) {
            if (a.name.equals(accountName)) {
                account = a;
                break;
            }
        }
        if (account == null) {
            Log.e(TAG, "unable to get an account with the same name in the settings");
            return null;
        }
        mAccount = account; // 保存当前账号

        // 获取认证令牌
        AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
                "goanna_mobile", null, activity, null, null);
        try {
            Bundle authTokenBundle = accountManagerFuture.getResult();
            authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
            if (invalidateToken) {
                // 失效现有令牌并重新获取（用于处理令牌过期）
                accountManager.invalidateAuthToken("com.google", authToken);
                loginGoogleAccount(activity, false);
            }
        } catch (Exception e) {
            Log.e(TAG, "get auth token failed");
            authToken = null;
        }

        return authToken;
    }

    /**
     * 尝试登录 Google Tasks 服务
     * @param activity Activity 上下文
     * @param authToken Google 认证令牌
     * @return 是否登录成功
     */
    private boolean tryToLoginGtask(Activity activity, String authToken) {
        if (!loginGtask(authToken)) { // 首次登录失败
            // 重新获取令牌并再次尝试
            authToken = loginGoogleAccount(activity, true);
            if (authToken == null) {
                Log.e(TAG, "login google account failed");
                return false;
            }
            if (!loginGtask(authToken)) { // 再次登录失败
                Log.e(TAG, "login gtask failed");
                return false;
            }
        }
        return true;
    }

    /**
     * 使用认证令牌登录 Google Tasks 服务
     * @param authToken Google 认证令牌
     * @return 是否登录成功
     */
    private boolean loginGtask(String authToken) {
        int timeoutConnection = 10000; // 连接超时 10 秒
        int timeoutSocket = 15000; // 套接字超时 15 秒
        HttpParams httpParameters = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
        mHttpClient = new DefaultHttpClient(httpParameters); // 创建带超时配置的 HTTP 客户端
        BasicCookieStore localBasicCookieStore = new BasicCookieStore();
        mHttpClient.setCookieStore(localBasicCookieStore); // 使用内存 Cookie 存储
        HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);

        try {
            // 构造登录 URL（携带认证令牌）
            String loginUrl = mGetUrl + "?auth=" + authToken;
            HttpGet httpGet = new HttpGet(loginUrl);
            HttpResponse response = mHttpClient.execute(httpGet); // 发送 GET 请求

            // 检查响应中的认证 Cookie
            List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
            boolean hasAuthCookie = false;
            for (Cookie cookie : cookies) {
                if (cookie.getName().contains("GTL")) { // GTL Cookie 是 Tasks 认证标识
                    hasAuthCookie = true;
                }
            }
            if (!hasAuthCookie) {
                Log.w(TAG, "it seems that there is no auth cookie");
            }

            // 解析响应获取客户端版本号
            String resString = getResponseContent(response.getEntity());
            String jsBegin = "_setup(";
            String jsEnd = ")}</script>";
            int begin = resString.indexOf(jsBegin);
            int end = resString.lastIndexOf(jsEnd);
            String jsString = null;
            if (begin != -1 && end != -1 && begin < end) {
                jsString = resString.substring(begin + jsBegin.length(), end);
            }
            JSONObject js = new JSONObject(jsString);
            mClientVersion = js.getLong("v"); // 保存客户端版本号

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            return false;
        } catch (Exception e) {
            Log.e(TAG, "httpget gtask_url failed");
            return false;
        }

        return true;
    }

    /**
     * 生成唯一操作 ID（自增计数器）
     * @return 操作 ID
     */
    private int getActionId() {
        return mActionId++;
    }

    /**
     * 创建 POST 请求对象（通用方法）
     * @return HttpPost 对象
     */
    private HttpPost createHttpPost() {
        HttpPost httpPost = new HttpPost(mPostUrl);
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        httpPost.setHeader("AT", "1"); // 可能用于标识请求类型或版本
        return httpPost;
    }

    /**
     * 解析 HTTP 响应内容（处理压缩格式）
     * @param entity HTTP 实体对象
     * @return 响应内容字符串
     * @throws IOException 输入输出异常
     */
    private String getResponseContent(HttpEntity entity) throws IOException {
        String contentEncoding = null;
        if (entity.getContentEncoding() != null) {
            contentEncoding = entity.getContentEncoding().getValue();
            Log.d(TAG, "encoding: " + contentEncoding);
        }

        InputStream input = entity.getContent();
        // 处理 GZIP 压缩响应
        if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
            input = new GZIPInputStream(entity.getContent());
        // 处理 DEFLATE 压缩响应
        } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
            Inflater inflater = new Inflater(true);
            input = new InflaterInputStream(entity.getContent(), inflater);
        }

        try {
            // 读取响应内容为字符串
            InputStreamReader isr = new InputStreamReader(input);
            BufferedReader br = new BufferedReader(isr);
            StringBuilder sb = new StringBuilder();
            String buff;
            while ((buff = br.readLine()) != null) {
                sb.append(buff);
            }
            return sb.toString();
        } finally {
            input.close(); // 确保流关闭
        }
    }

    /**
     * 发送 POST 请求并解析 JSON 响应
     * @param js 请求体 JSON 对象
     * @return 响应 JSON 对象
     * @throws NetworkFailureException 网络异常
     */
    private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
        if (!mLoggedin) {
            Log.e(TAG, "please login first");
            throw new ActionFailureException("not logged in"); // 未登录时抛出异常
        }

        HttpPost httpPost = createHttpPost();
        try {
            // 构造 POST 请求参数（将 JSON 放入请求体）
            LinkedList<BasicNameValuePair> list = new LinkedList<>();
            list.add(new BasicNameValuePair("r", js.toString()));
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
            httpPost.setEntity(entity);

            // 执行 POST 请求
            HttpResponse response = mHttpClient.execute(httpPost);
            String jsString = getResponseContent(response.getEntity());
            return new JSONObject(jsString); // 解析响应为 JSON

        } catch (ClientProtocolException e) {
            Log.e(TAG, e.toString());
            throw new NetworkFailureException("postRequest failed"); // 协议异常（网络问题）
        } catch (IOException e) {
            Log.e(TAG, e.toString());
            throw new NetworkFailureException("postRequest failed"); // 输入输出异常（网络问题）
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            throw new ActionFailureException("unable to convert response content to jsonobject"); // JSON 解析失败（数据格式问题）
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            throw new ActionFailureException("error occurs when posting request"); // 其他异常
        }
    }

    /**
     * 创建任务（调用 Google Tasks API）
     * @param task 要创建的任务对象
     * @throws NetworkFailureException 网络异常
     */
    public void createTask(Task task) throws NetworkFailureException {
        commitUpdate(); // 提交之前的批量操作（如果有）
        try {
            JSONObject jsPost = new JSONObject();
            JSONArray actionList = new JSONArray();

            // 添加创建任务的操作到请求中
            actionList.put(task.getCreateAction(getActionId()));
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 携带客户端版本号

            // 发送请求并解析响应
            JSONObject jsResponse = postRequest(jsPost);
            JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
                    GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
            task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 保存任务的 Google ID

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            throw new ActionFailureException("create task: handing jsonobject failed");
        }
    }

    /**
     * 创建任务列表（调用 Google Tasks API）
     * @param tasklist 要创建的任务列表对象
     * @throws NetworkFailureException 网络异常
     */
    public void createTaskList(TaskList tasklist) throws NetworkFailureException {
        commitUpdate(); // 提交之前的批量操作
        try {
            JSONObject jsPost = new JSONObject