"""
API 模块
"""

import requests
from typing import Optional, List, Dict, Any
from config import Config
from decimal import Decimal
import time
import numpy as np
from log_config import configure_task_logger, configure_error_task_logger
import json

# 获取已经配置好的常规日志记录器
logger = configure_task_logger()

# 获取已经配置好的错误任务日志记录器
error_task_logger = configure_error_task_logger()


class NpEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.integer):
            return int(obj)
        elif isinstance(obj, np.floating):
            return float(obj)
        elif isinstance(obj, np.ndarray):
            return obj.tolist()
        else:
            return super(NpEncoder, self).default(obj)


def replace_decimals(obj):
    if isinstance(obj, dict):
        return {k: replace_decimals(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [replace_decimals(item) for item in obj]
    elif isinstance(obj, Decimal):
        return float(obj)  # 或者 str(obj)
    return obj


class API:
    def entry_data_get(self, data: dict, replace: bool = False) -> Dict:  # 获取单条表单数据
        """
        获取单条表单数据
        :param replace: 是否替换字段,默认为关
        :param data: 简道云插件发送过来的data，包含应用id、表单id、数据id等信息
        :return:
        """

        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/get'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 app_key
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "app_id": data['api_key'],  # 应用ID
            "entry_id": data['entry_id'],  # 表单ID
            "data_id": data['data_id']  # 数据ID
        })

        res = requests.post(url=url, data=payload, headers=headers, timeout=10)
        data_get = res.json()

        if replace:
            data_get = self.field_replacement(data, data_get)  # 字段替换，由id替换为标签名

        return data_get

    def entry_data_list(self, data: dict, replace: bool = False, max_retries: int = 20) -> Dict:  # 获取多条表单数据
        """
        获取多条表单数据
        :param max_retries: 最大重试次数
        :param replace: 是否替换字段
        :param data:
                    api_key: 应用id
                    entry_id: 表单id
        :return:
        """

        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/list'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 app_key
            'Content-Type': 'application/json'
        }
        all_data_batches = []  # 用于存储每次请求返回的数据批次
        last_data_id = None
        exit_flag = False
        while True:
            payload = json.dumps({
                "app_id": data['api_key'],  # 应用ID
                "entry_id": data['entry_id'],  # 表单ID
                "limit": 100,
                "data_id": last_data_id
            })
            retries = 0
            while retries <= max_retries:
                try:
                    res = requests.post(url=url, data=payload, headers=headers, timeout=10)
                    res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                    data_get = res.json()
                    if data_get["data"]:
                        all_data_batches.extend(data_get['data'])
                        last_data_id = data_get['data'][-1].get('_id')
                        logger.info(f"已获取 {len(all_data_batches)} 条数据")
                        break  # 成功则跳出循环
                    else:
                        if 'data' not in data_get or len(data_get['data']) == 0:
                            exit_flag = True
                            break
                        logger.warning(f"请求异常, 将重新请求")
                        retries += 1
                        time.sleep(0.1)  # 在重试之间稍作停顿
                except requests.exceptions.RequestException as e:
                    logger.warning(f"请求异常: {e}, 将重新请求")
                    retries += 1
                    time.sleep(0.1)  # 在重试之间稍作停顿
            if retries > max_retries:
                error_task_logger.error(f"任务 {last_data_id}组 连续{max_retries}次请求失败，放弃此次请求。")
                all_data_batches.append(None)  # 或者可以选择记录失败的payload以便后续处理

            if exit_flag:
                break

        # 构建最终返回的字典
        final_data = {
            'data': all_data_batches  # 'data' 键对应的值是列表的列表
        }
        if replace:
            print("进行了替换")
            return_data = self.field_replacement(data, final_data)  # 字段替换，由id替换为标签名

            return return_data
        else:
            return final_data

    @staticmethod
    def entry_widget_list(data: dict) -> Optional[Dict[str, Any]]:  # 获取表单字段
        """
        获取表单字段
        :param data: 简道云插件发送过来的data，包含应用id、表单id、数据id等信息
        :return:
        """

        url = 'https://api.jiandaoyun.com/api/v5/app/entry/widget/list'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 app_key
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "app_id": data['api_key'],
            "entry_id": data['entry_id'],
        })

        res = requests.post(url=url, data=payload, headers=headers, timeout=10)
        return res.json()

    def field_replacement(self, data: dict, data_get: dict) -> dict:
        """
        字段替换，将id替换为标签名，即唯一值替换为表单中显示字段的名字
        :param data: 简道云插件发送过来的data，包含表单id、数据id、应用id
        :param data_get: 简道云请求的数据，一般是根据数据id获取到表单的数据
        :return: 将根据数据id获取到的表单数据，进行替换，返回替换后的数据
        """

        # 获取表单对应字段标签名称
        widget_list = self.entry_widget_list(data)

        # 检查widget_list是否有效
        if not widget_list or 'widgets' not in widget_list or not isinstance(widget_list['widgets'], list):
            raise ValueError("映射表没有接受到数据")

        # 创建一个映射表，将_widget_名称映射到label
        name_to_label = {widget['name']: widget['label'] for widget in widget_list['widgets']}

        def replace_keys(obj):
            """递归替换字典中的键名"""
            if isinstance(obj, dict):
                new_dict = {}
                for key, value in obj.items():
                    new_key = name_to_label.get(key, key)
                    new_dict[new_key] = replace_keys(value)
                return new_dict
            elif isinstance(obj, list):
                return [replace_keys(item) for item in obj]
            else:
                return obj

        # 复制 data_get，避免修改原始数据
        data_get_copy = json.loads(json.dumps(data_get))  # 深拷贝

        # 替换 data 字段下的所有键
        if 'data' in data_get_copy:
            data_get_copy['data'] = replace_keys(data_get_copy['data'])

        return data_get_copy

    @staticmethod
    def data_batch_create(data: dict, max_retries: int = 20) -> Optional[requests.Response]:  # 新建单条数据
        """
        新建单条表单数据
        :param max_retries: 最大重试次数
        :param data: 应该包含应用id、表单id，以及新建的数据data['data']
        :return: 返回创建后简道云返回的信息
        """
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/create'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 app_key
            'Content-Type': 'application/json'
        }

        """
        data 样式 # 后续优化发送数据样式 目前输入字段，后续优化输入表单名称
        jiandaoyun_data['data'] = {"_widget_1731650067055":{"value":f'{username}{password}'},
        "_widget_1731650067056":{"value": f"{group}"}}
        """
        # noinspection DuplicatedCode
        payload = json.dumps({
            "app_id": data['api_key'],  # 应用ID
            "entry_id": data['entry_id'],  # 表单ID
            "data": data['data'],
            "is_start_workflow": data.get('is_start_workflow', "false"),
            "is_start_trigger": data.get('is_start_trigger', "false"),
            "transaction_id": data.get('transaction_id', "")
        }
        )

        retries = 0
        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                data_get = res.json()
                if res.status_code == 200:
                    return data_get
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(3)  # 在重试之间稍作停顿
        if retries > max_retries:
            error_task_logger.error(
                f"任务 {data['data_list']} 连续{max_retries}次请求失败，放弃此次请求。")
        return None

    @staticmethod
    def entry_data_batch_create(
            data: dict,
            chunk_size: int = 90,
            max_retries: int = 20
    ) -> List[Optional[requests.Response]]:  # 新建多条数据 注意简道云限制1次最多100条数据
        """
        新建多条数据
        :param max_retries: 最大重试次数，此处设置20次
        :param data:应包含数据id、表单id、以及需要新建的信息，新建信息应该是一个列表
        :param chunk_size: 简道云限制批量新建一次最多100条，这里默认值设置为90条一次
        :return:返回请求后的结果
        """
        data = replace_decimals(data)
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/batch_create'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        """
        data_list 样式 # 后续优化发送数据样式 目前输入字段，后续优化输入表单名称
        jiandaoyun_data_list['data_list'] = [{"_widget_1731650067055":{"value":f'{username}{password}'},
                                             "_widget_1731650067056":{"value": f"{group}"}},
                                             {"_widget_1731650067055":{"value":f'{username}{password}'},
                                             "_widget_1731650067056":{"value": f"{group}"}}]
        """

        # 获取data_list长度
        total_length = len(data['data_list'])
        logger.info(f"多数据写入行数: {total_length}")

        # 计算需要发送的次数
        num_chunks = (total_length + chunk_size - 1) // chunk_size  # //整除向下取证，需要加上chunk_size - 1保证不会有缺失数据
        data_get_list = []
        for i in range(num_chunks):
            start_index = i * chunk_size
            end_index = min(start_index + chunk_size, total_length)
            payload = json.dumps({
                "app_id": data['api_key'],  # 应用ID
                "entry_id": data['entry_id'],  # 表单ID
                "data_list": data['data_list'][start_index:end_index],
                "is_start_workflow": data.get('is_start_workflow', "false"),
                "is_start_trigger": data.get('is_start_trigger', "false"),
            }, cls=NpEncoder)
            retries = 0
            while retries <= max_retries:
                try:
                    res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                    res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                    data_get = res.json()
                    if data_get["status"] == "success":
                        data_get_list.append(data_get)
                        break  # 成功则跳出循环
                    else:
                        logger.warning(f"请求异常,将重新请求")
                        retries += 1
                        time.sleep(3)  # 在重试之间稍作停顿
                except requests.exceptions.RequestException as e:
                    logger.warning(f"请求异常: {e}, 将重新请求")
                    retries += 1
                    time.sleep(0.1)  # 在重试之间稍作停顿
            if retries > max_retries:
                error_task_logger.error(
                    f"任务 {data['data_list'][start_index:end_index]} 连续{max_retries}次请求失败，放弃此次请求。")
                data_get_list.append(None)  # 或者可以选择记录失败的payload以便后续处理

        return data_get_list

    @staticmethod
    def entry_data_update(data: dict, max_retries: int = 20) -> dict:  # 修改数据
        """
        修改数据
        :param max_retries: 最大重试次数，此处设置100次
        :param data: 简道云插件发送过来的data，包含应用id、表单id、数据id等信息
        :return: 修改数据后简道云返回的结果
        """
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/update'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "app_id": data['api_key'],  # 应用ID
            "entry_id": data['entry_id'],  # 表单ID
            "data_id": data['data_id'],  # 数据ID
            "data": data['data']
        }
        )

        data_get = None
        retries = 0
        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                data_get = res.json()
                if res.status_code == 200:
                    break  # 成功则跳出循环
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(10)  # 在重试之间稍作停顿
            if retries > max_retries:
                error_task_logger.error(f"任务 {data['data_id']} 连续{max_retries}次请求失败，放弃此次请求。")
                continue
        return data_get

    @staticmethod
    def entry_data_delete(data: dict, max_retries: int = 20, ) -> dict:
        """
        删除单条数据
        :param data: 应包含应用ID、表单ID、数据ID
        :param max_retries: 最大重试次数，默认20
        :return:
        """
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/delete'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "app_id": data['api_key'],  # 应用ID
            "entry_id": data['entry_id'],  # 表单ID
            "data_id": data['data_id'],  # 数据ID
        }
        )
        retries = 0
        delete_status = None

        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                delete_status = res.json()
                # 手动处理状态码 4001
                if delete_status == {
                    "code": 4001,
                    "msg": "Data does not exist."
                }:
                    logger.info(f"返回结果:, {delete_status}")
                    break  # 成功则跳出循环

                # 检查其他状态码
                res.raise_for_status()  # 只对非 4001 的状态码进行检查

                logger.info(f"返回结果:, {delete_status}")
                if res.status_code == 200:
                    break  # 成功则跳出循环
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(10)  # 在重试之间稍作停顿
            if retries > max_retries:
                error_task_logger.error(f"任务 {data['data_id']} 连续{max_retries}次请求失败，放弃此次请求。")
                continue
        return delete_status

    @staticmethod
    def entry_data_batch_delete(
            data: dict,
            chunk_size: int = 90,
            max_retries: int = 20
    ) -> List[Optional[requests.Response]]:  # 新建多条数据 注意简道云限制1次最多100条数据
        """
        批量删除数据
        :param data: 应包含应用ID、表单ID、数据ID列表
        :param chunk_size:单词删除最大条数，默认90
        :param max_retries:重试次数，默认20
        :return:
        """

        data = replace_decimals(data)
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/batch_delete'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        # 获取data_list长度
        total_length = len(data['data_ids'])
        logger.info(f"多数据删除行数: {total_length}")

        # 计算需要发送的次数
        num_chunks = (total_length + chunk_size - 1) // chunk_size  # //整除向下取证，需要加上chunk_size - 1保证不会有缺失数据
        data_get_list = []

        for i in range(num_chunks):
            start_index = i * chunk_size
            end_index = min(start_index + chunk_size, total_length)
            payload = json.dumps({
                "app_id": data['api_key'],  # 应用ID
                "entry_id": data['entry_id'],  # 表单ID
                "data_ids": data['data_ids'][start_index:end_index],
            }, cls=NpEncoder)

            retries = 0
            while retries <= max_retries:
                try:
                    res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                    res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                    data_get = res.json()
                    logger.info(f"{i}页 返回结果: {data_get}")
                    if data_get["status"] == "success":
                        data_get_list.append(data_get)
                        break  # 成功则跳出循环
                    else:
                        logger.warning(f"请求异常, 将重新请求")
                        retries += 1
                        time.sleep(3)  # 在重试之间稍作停顿
                except requests.exceptions.RequestException as e:
                    logger.warning(f"请求异常: {e}, 将重新请求")
                    retries += 1
                    time.sleep(0.1)  # 在重试之间稍作停顿
            if retries > max_retries:
                error_task_logger.error(
                    f"任务 {data['data_list'][start_index:end_index]} 连续{max_retries}次请求失败，放弃此次请求。")
                data_get_list.append(None)  # 或者可以选择记录失败的payload以便后续处理

        return data_get_list

    @staticmethod
    def workflow_instance_get(data: dict, max_retries: int = 20) -> dict:
        """
        查询实例流程信息
        :param max_retries:
        :param data: 简道云插件发送过来的data，包含应用id
        :return: 查询简道云流程实例信息返回的结果
        """
        url = 'https://api.jiandaoyun.com/api/v5/workflow/instance/get'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "instance_id": data['data_id'],
            "tasks_type": 1
        }
        )
        data_get = None
        retries = 0
        while retries <= max_retries:
            try:
                res = requests.post(url=url, data=payload, headers=headers, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                data_get = res.json()
                # print( "返回结果:", data_get)
                if res.status_code == 200:
                    break  # 成功则跳出循环
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(0.1)  # 在重试之间稍作停顿
        if retries > max_retries:
            error_task_logger.error(f"任务 {data['data_id']} 连续{max_retries}次请求失败，放弃此次请求。")

        return data_get

    @staticmethod
    def workflow_task_approve(data: dict) -> dict:
        """
        流程待办提交
        :param data:应包含username、instance_id(data_id)、task_id等信息
        :return:返回简道云流程待办提交的结果
        """
        url = 'https://api.jiandaoyun.com/api/v1/workflow/task/approve'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "username": data["username"],
            "instance_id": data["instance_id"],
            "task_id": data['task_id'],
            "comment": "自动转交"
        }
        )

        res = requests.post(url=url, data=payload, headers=headers, timeout=10)
        return res.json()

    @staticmethod
    def workflow_task_hand_over(data: dict, max_retries: int = 10) -> dict | None:
        """
        流程待办转交
        :param max_retries: 最大重试次数
        :param data:应包含username、instance_id(data_id)、task_id等信息
        :return:返回简道云流程待办转交的结果
        """
        url = 'https://api.jiandaoyun.com/api/v1/workflow/task/transfer'

        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "username": data["username"],  # 当前节点的负责人
            "instance_id": data["instance_id"],
            "task_id": data['task_id'],
            "transfer_username": data['transfer_username'],  # 转交人
            "comment": "转交"
        }
        )

        retries = 0
        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                if res.status_code == 200:
                    return res.json()
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(3)  # 在重试之间稍作停顿
        if retries > max_retries:
            error_task_logger.error(
                f"任务 {data['data_list']} 连续{max_retries}次请求失败，放弃此次请求。")
        return None

    @staticmethod
    def get_upload_token(data: dict, max_retries: int = 10) -> dict[str, Any] | None:
        """
        获取文件上传凭证
        :param max_retries: 最大重试次数
        :param data: 应包含应用ID、表单ID、事务ID
        :return: 返回upload_url、upload_token
        """
        url = 'https://api.jiandaoyun.com/api/v5/app/entry/file/get_upload_token'
        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            'Content-Type': 'application/json'
        }

        payload = json.dumps({
            "app_id": data['api_key'],  # 应用ID
            "entry_id": data['entry_id'],  # 表单ID
            "transaction_id": data['transaction_id'],  # 事务ID
        })

        retries = 0
        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                res_j = res.json()
                upload_url = res_j['token_and_url_list'][0]['url']
                upload_token = res_j['token_and_url_list'][0]['token']
                logger.info(f"返回结果: {upload_url}, {upload_token}")
                if res.status_code == 200:
                    return {
                        'upload_url': upload_url,
                        'upload_token': upload_token
                    }
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(3)  # 在重试之间稍作停顿
        if retries > max_retries:
            error_task_logger.error(
                f"任务 {data['data_list']} 连续{max_retries}次请求失败，放弃此次请求。")
        return None

    @staticmethod
    def upload_file(data: dict, max_retries: int = 10) -> Any | None:
        """
        上传文件
        :param max_retries: 最大重试次数
        :param data: 应包含上传文件路径、上传文件url、上传文件token
        :return: 返回上传文件结果
        """
        url = data['upload_url']
        headers = {
            'Authorization': Config.JIANDAOYUN_API_TOKEN,  # 曹伟应用api测试 appKey
            # 'Content-Type': 'application/json'
        }
        file_path = data['file_path']  # 上传文件路径
        payload = {
            "token": data['upload_token'],  # 上传文件token
        }
        f = open(file_path, 'rb')
        files = {
            "file": f
        }

        retries = 0
        while retries <= max_retries:
            try:
                res: requests.Response = requests.post(url=url, data=payload, headers=headers, files=files, timeout=10)
                res.raise_for_status()  # 检查HTTP响应状态码，如果不等于200会抛出异常
                data_get = res.json()
                logger.info(f"返回结果: {data_get}")
                if res.status_code == 200:
                    return data_get
                else:
                    logger.warning(f"请求异常, 将重新请求")
                    retries += 1
                    time.sleep(3)  # 在重试之间稍作停顿
            except requests.exceptions.RequestException as e:
                logger.warning(f"请求异常: {e}, 将重新请求")
                retries += 1
                time.sleep(3)  # 在重试之间稍作停顿
        if retries > max_retries:
            error_task_logger.error(
                f"任务 {data['data_list']} 连续{max_retries}次请求失败，放弃此次请求。")

        f.close()
        return None
