/*
 * 版权所有 (c) 2010-2011，MiCode 开源社区 (www.micode.net)
 * 根据 Apache 许可证 2.0 版本（"许可证"）授权；
 * 除非符合许可证的规定，否则不得使用本文件。
 * 您可以从以下网址获取许可证副本：
 * http://www.apache.org/licenses/LICENSE-2.0
 * 除非适用法律要求或书面同意，本软件按"原样"分发，
 * 没有任何明示或暗示的保证或条件。
 * 详见许可证中规定的权限和限制。
 * （注：这是一份标准的Apache许可证2.0版本的开源声明）
 */

// 定义Google任务远程同步包路径
package net.micode.notes.gtask.remote;
// 导入Android账号管理相关类
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;                          // 日志工具
// 导入AndroidX注解
import androidx.annotation.NonNull;                // 非空参数注解
// 导入Google任务数据模型
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;     // Google任务字符串常量工具
import net.micode.notes.ui.NotesPreferenceActivity;  // 设置界面Activity
// 导入Apache HTTP组件（已废弃，但部分旧系统仍在使用）
import org.apache.http.HttpEntity;                 // HTTP消息实体接口
import org.apache.http.HttpResponse;               // HTTP响应对象
import org.apache.http.client.entity.UrlEncodedFormEntity;  // URL编码表单实体
import org.apache.http.client.methods.HttpGet;     // HTTP GET请求方法
import org.apache.http.client.methods.HttpPost;    // HTTP POST请求方法
import org.apache.http.cookie.Cookie;              // Cookie信息对象
import org.apache.http.impl.client.BasicCookieStore;  // 基础Cookie存储
import org.apache.http.impl.client.DefaultHttpClient;  // 默认HTTP客户端（已废弃）
import org.apache.http.message.BasicNameValuePair;  // 键值对参数对象
import org.apache.http.params.BasicHttpParams;     // HTTP参数集合
import org.apache.http.params.HttpConnectionParams;  // 连接相关参数
import org.apache.http.params.HttpParams;          // HTTP参数接口
import org.apache.http.params.HttpProtocolParams;   // 协议相关参数
// 导入JSON处理类
import org.json.JSONArray;                        // JSON数组处理
import org.json.JSONException;                    // JSON解析异常
import org.json.JSONObject;                       // JSON对象处理
// 导入IO流相关类
import java.io.BufferedReader;                    // 缓冲字符输入流
import java.io.IOException;                       // IO异常
import java.io.InputStream;                       // 字节输入流
import java.io.InputStreamReader;                 // 字节流转字符流
import java.util.LinkedList;                      // 链表实现
import java.util.List;                            // 列表接口
// 导入压缩流处理
import java.util.zip.GZIPInputStream;             // GZIP解压流
import java.util.zip.Inflater;                     // 解压器
import java.util.zip.InflaterInputStream;          // 解压输入流

// Google任务客户端实现类，负责与Google Tasks API交互
public class GTaskClient {
    // 日志标签
    private static final String TAG = GTaskClient.class.getSimpleName();
    // Google任务基础URL
    private static final String GTASK_URL = "https://mail.google.com/tasks/";
    // GET请求URL
    private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
    // POST请求URL
    private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
    // 单例实例
    private static GTaskClient mInstance = null;
    // HTTP客户端（使用已废弃的Apache HTTP Client）
    private DefaultHttpClient mHttpClient;
    // GET请求实际URL（可能包含自定义域名）
    private String mGetUrl;
    // POST请求实际URL（可能包含自定义域名）
    private String mPostUrl;
    // 客户端版本号
    private long mClientVersion;
    // 登录状态标识
    private boolean mLoggedin;
    // 上次登录时间戳
    private long mLastLoginTime;
    // 操作ID计数器
    private int mActionId;
    // 当前账号信息
    private Account mAccount;
    // 待提交的更新操作集合
    private JSONArray mUpdateArray;
    // 私有构造方法（单例模式）
    private GTaskClient() {
        mHttpClient = null;
        mGetUrl = GTASK_GET_URL;  // 默认使用官方GET URL
        mPostUrl = GTASK_POST_URL;  // 默认使用官方POST URL
        mClientVersion = -1;  // 初始无效版本
        mLoggedin = false;  // 初始未登录
        mLastLoginTime = 0;  // 初始时间戳
        mActionId = 1;  // 操作ID从1开始
        mAccount = null;  // 初始无账号
        mUpdateArray = null;  // 初始无待更新操作
    }
    // 获取单例实例（线程安全）
    public static synchronized GTaskClient getInstance() {
        if (mInstance == null) {
            mInstance = new GTaskClient();  // 懒加载初始化
        }
        return mInstance;
    }
    // 登录方法
    public boolean login(Activity activity) {
        // 检查登录是否过期（假设cookie 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, "已经登录");
            return true;
        }

        mLastLoginTime = System.currentTimeMillis();   // 更新登录时间
        String authToken = loginGoogleAccount(activity, false);  // 获取认证令牌
        if (authToken == null) {
            Log.e(TAG, "Google账号登录失败");
            return false;
        }

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

            // 尝试用自定义域名登录
            if (tryToLoginGtask(activity, authToken)) {
                mLoggedin = true;
            }
        }

        // 自定义域名登录失败时尝试官方URL
        if (!mLoggedin) {
            mGetUrl = GTASK_GET_URL;  // 恢复默认GET URL
            mPostUrl = GTASK_POST_URL;  // 恢复默认POST URL
            if (!tryToLoginGtask(activity, authToken)) {
                return false;
            }
        }

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

    // 获取Google账号认证令牌
    private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
        String authToken;
        // 获取账号管理器实例
        AccountManager accountManager = AccountManager.get(activity);
        // 获取所有Google账号
        Account[] accounts = accountManager.getAccountsByType("com.google");

        if (accounts.length  == 0) {
            Log.e(TAG, "没有可用的Google账号");
            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) {
            mAccount = account;  // 保存当前账号
        } else {
            Log.e(TAG, "找不到设置中配置的账号");
            return null;
        }

        // 异步获取认证令牌
        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, "获取认证令牌失败");
            authToken = null;
        }

        return authToken;
    }

    // 尝试登录Google Tasks服务
    private boolean tryToLoginGtask(Activity activity, String authToken) {
        if (loginGtask(authToken)) {
            // 令牌可能过期，失效后重试
            authToken = loginGoogleAccount(activity, true);
            if (authToken == null) {
                Log.e(TAG, "Google账号登录失败");
                return false;
            }

            if (loginGtask(authToken)) {
                Log.e(TAG, "Google Tasks登录失败");
                return false;
            }
        }
        return true;
    }

    // 实际执行Google Tasks登录
    private boolean loginGtask(String authToken) {
        // 设置HTTP请求超时（连接10秒，socket15秒）
        int timeoutConnection = 10000;
        int timeoutSocket = 15000;
        HttpParams httpParameters = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParameters,  timeoutConnection);
        HttpConnectionParams.setSoTimeout(httpParameters,  timeoutSocket);

        // 初始化HTTP客户端
        mHttpClient = new DefaultHttpClient(httpParameters);
        BasicCookieStore localBasicCookieStore = new BasicCookieStore();
        mHttpClient.setCookieStore(localBasicCookieStore);   // 设置cookie存储
        HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(),  false);

        // 执行登录请求
        try {
            String loginUrl = mGetUrl + "?auth=" + authToken;  // 构造登录URL
            HttpGet httpGet = new HttpGet(loginUrl);
            HttpResponse response = mHttpClient.execute(httpGet);

            // 检查认证cookie
            List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
            boolean hasAuthCookie = false;
            for (Cookie cookie : cookies) {
                if (cookie.getName().contains("GTL"))  {
                    hasAuthCookie = true;
                }
            }
            if (!hasAuthCookie) {
                Log.w(TAG, "缺少认证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 true;  // 解析失败返回true触发重试
        } catch (Exception e) {
            Log.e(TAG, "HTTP GET请求失败");
            return true;  // 网络异常返回true触发重试
        }

        return false;  // 登录成功返回false
    }

    // 生成递增的操作ID
    private int getActionId() {
        return mActionId++;
    }

    // 创建HTTP POST请求对象
    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实体获取响应内容（支持gzip/deflate压缩）
    private String getResponseContent(HttpEntity entity) throws IOException {
        String contentEncoding = null;
        if (entity.getContentEncoding()  != null) {
            contentEncoding = entity.getContentEncoding().getValue();
            Log.d(TAG, "内容编码: " + contentEncoding);
        }

        InputStream input = entity.getContent();
        // 根据编码类型选择解压方式
        if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip"))  {
            input = new GZIPInputStream(entity.getContent());
        } 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();

            while (true) {
                String buff = br.readLine();
                if (buff == null) {
                    return sb.toString();   // 返回完整内容
                }
                sb = sb.append(buff);
            }
        } finally {
            input.close();   // 确保流关闭
        }
    }

    // 执行POST请求并返回JSON响应
    private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
        if (!mLoggedin) {
            Log.e(TAG, "请先登录");
            throw new ActionFailureException("未登录");
        }

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

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

        } catch (IOException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("POST请求失败");
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("响应内容转JSON失败");
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("请求过程中发生错误");
        }
    }

    // 创建任务
    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));

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("创建任务时处理JSON失败");
        }
    }

    // 创建任务列表
    public void createTaskList(TaskList tasklist) throws NetworkFailureException {
        commitUpdate();  // 先提交待更新操作
        try {
            JSONObject jsPost = new JSONObject();
            JSONArray actionList = new JSONArray();

            // 添加创建动作
            actionList.put(tasklist.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);
            tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("创建任务列表时处理JSON失败");
        }
    }

    // 提交所有待更新操作
    public void commitUpdate() throws NetworkFailureException {
        if (mUpdateArray != null) {  // 有待更新操作时才执行
            try {
                JSONObject jsPost = new JSONObject();

                // 添加动作列表
                jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST,  mUpdateArray);

                // 添加客户端版本
                jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION,  mClientVersion);

                postRequest(jsPost);  // 发送请求
                mUpdateArray = null;  // 清空待更新操作
            } catch (JSONException e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
                throw new ActionFailureException("提交更新时处理JSON失败");
            }
        }
    }

    // 添加节点更新操作
    public void addUpdateNode(Node node) throws NetworkFailureException {
        if (node != null) {
            // 避免过多更新操作（限制最多10个）
            if (mUpdateArray != null && mUpdateArray.length()  > 10) {
                commitUpdate();  // 超过限制先提交
            }

            if (mUpdateArray == null)
                mUpdateArray = new JSONArray();
            mUpdateArray.put(node.getUpdateAction(getActionId()));   // 添加更新动作
        }
    }

    // 移动任务
    public void moveTask(Task task, TaskList preParent, TaskList curParent)
            throws NetworkFailureException {
        commitUpdate();  // 先提交待更新操作
        try {
            JSONObject jsPost = new JSONObject();
            JSONArray actionList = new JSONArray();
            JSONObject action = new JSONObject();

            // 设置移动动作参数
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
                    GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID,  getActionId());
            action.put(GTaskStringUtils.GTASK_JSON_ID,  task.getGid());

            // 同列表移动时设置前驱任务ID
            if (preParent == curParent && task.getPriorSibling()  != null) {
                action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID,  task.getPriorSibling());
            }

            action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST,  preParent.getGid());
            action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT,  curParent.getGid());

            // 跨列表移动时设置目标列表ID
            if (preParent != curParent) {
                action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST,  curParent.getGid());
            }

            actionList.put(action);
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST,  actionList);

            // 添加客户端版本
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION,  mClientVersion);

            postRequest(jsPost);  // 发送请求

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("移动任务时处理JSON失败");
        }
    }

    // 删除节点
    public void deleteNode(Node node) throws NetworkFailureException {
        commitUpdate();  // 先提交待更新操作
        try {
            JSONObject jsPost = new JSONObject();
            JSONArray actionList = new JSONArray();

            // 设置删除标记并添加更新动作
            node.setDeleted(true);
            actionList.put(node.getUpdateAction(getActionId()));
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST,  actionList);

            // 添加客户端版本
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION,  mClientVersion);

            postRequest(jsPost);  // 发送请求
            mUpdateArray = null;  // 清空待更新操作
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("删除节点时处理JSON失败");
        }
    }

    // 获取所有任务列表
    public JSONArray getTaskLists() throws NetworkFailureException {
        if (!mLoggedin) {
            Log.e(TAG, "请先登录");
            throw new ActionFailureException("未登录");
        }

        try {
            HttpGet httpGet = new HttpGet(mGetUrl);  // 创建GET请求
            HttpResponse response = mHttpClient.execute(httpGet);

            // 从响应中提取任务列表数据
            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);
            return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
        } catch (IOException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("获取任务列表时HTTP请求失败");
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("获取任务列表时处理JSON失败");
        }
    }
    // 获取特定任务列表详情
    public JSONArray getTaskList(String listGid) throws NetworkFailureException {
        commitUpdate();  // 先提交待更新操作
        try {
            JSONObject jsPost = new JSONObject();
            JSONArray actionList = getJsonArray(listGid);  // 获取动作数组
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST,  actionList);

            // 添加客户端版本
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION,  mClientVersion);

            JSONObject jsResponse = postRequest(jsPost);
            return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("获取任务列表详情时处理JSON失败");
        }
    }

    // 构造获取任务列表的动作数组
    @NonNull
    private JSONArray getJsonArray(String listGid) throws JSONException {
        JSONArray actionList = new JSONArray();
        JSONObject action = new JSONObject();
        // 设置获取全部动作参数
        action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
                GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
        action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID,  getActionId());
        action.put(GTaskStringUtils.GTASK_JSON_LIST_ID,  listGid);
        action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED,  false);  // 不获取已删除项
        actionList.put(action);
        return actionList;
    }
    // 获取当前同步账号
    public Account getSyncAccount() {
        return mAccount;
    }
    // 重置待更新操作数组
    public void resetUpdateArray() {
        mUpdateArray = null;
    }
}