import os
import re
import ssl
import urllib
import urllib.parse
import uuid
from io import BytesIO
from typing import List, Union

import requests
from PIL import Image

from qwen_agent.agents import FnCallAgent
from qwen_agent.gui import WebUI
from qwen_agent.llm.schema import ContentItem
from qwen_agent.tools.base import BaseToolWithFileAccess, register_tool

ROOT_RESOURCE = os.path.join(os.path.dirname(__file__), 'resource')


@register_tool('express_tracking')
class ExpressTracking(BaseToolWithFileAccess):
    API_URL = 'https://market.aliyun.com/apimarket/detail/cmapi021863#sku=yuncode15863000017'
    description = '全国快递物流查询-快递查询接口'
    parameters = [
        {
            'name': 'no',
            'type': 'string',
            'description': '快递单号 【顺丰和丰网请输入单号:收件人或寄件人手机号后四位。例如：123456789:1234】',
            'required': True
        },
        {
            'name': 'type',
            'type': 'string',
            'description': '快递公司字母简写：不知道可不填95%能自动识别，填写查询速度会更快',
            'required': False
        },
    ]

    def call(self, params: Union[str, dict], files: List[str] = None, **kwargs) -> str:
        super().call(params=params, files=files)
        params = self._verify_json_format_args(params)

        id = params['no'].strip()
        company = params.get('type', '').strip()

        host = 'https://wuliu.market.alicloudapi.com'
        path = '/kdi'
        appcode = os.environ['AppCode_ExpressTracking']  # 开通服务后 买家中心-查看AppCode
        querys = f'no={id}&type={company}'

        url = host + path + '?' + querys
        header = {'Authorization': 'APPCODE ' + appcode}
        res = requests.get(url, headers=header)
        httpStatusCode = res.status_code

        if (httpStatusCode == 200):
            # print("正常请求计费(其他均不计费)")
            import json
            try:
                out = json.loads(res.text)
            except json.decoder.JSONDecodeError:
                import json5
                out = json5.loads(res.text)
            return '```json' + json.dumps(out, ensure_ascii=False, indent=4) + '\n```'
        else:
            httpReason = res.headers['X-Ca-Error-Message']
            if (httpStatusCode == 400 and httpReason == 'Invalid Param Location'):
                return '参数错误'
            elif (httpStatusCode == 400 and httpReason == 'Invalid AppCode'):
                return 'AppCode错误'
            elif (httpStatusCode == 400 and httpReason == 'Invalid Url'):
                return '请求的 Method、Path 或者环境错误'
            elif (httpStatusCode == 403 and httpReason == 'Unauthorized'):
                return '服务未被授权（或URL和Path不正确）'
            elif (httpStatusCode == 403 and httpReason == 'Quota Exhausted'):
                return '套餐包次数用完'
            elif (httpStatusCode == 403 and httpReason == 'Api Market Subscription quota exhausted'):
                return '套餐包次数用完，请续购套餐'
            elif (httpStatusCode == 500):
                return 'API网关错误'
            else:
                return f'参数名错误 或 其他错误 \nhttpStatusCode:{httpStatusCode} \nhttpReason: {httpReason}'


@register_tool('area_to_weather')
class Area2Weather(BaseToolWithFileAccess):
    API_URL = 'https://market.aliyun.com/apimarket/detail/cmapi010812#sku=yuncode4812000017'
    description = '地名查询天气预报，调用此API查询未来7天的天气，不支持具体时刻天气查询'
    parameters = [
        {
            'name': 'area',
            'type': 'string',
            'description': '地区名称',
            'required': True
        },
        {
            'name': 'needMoreDay',
            'type': 'string',
            'description': '是否需要返回7天数据中的后4天。1为返回，0为不返回。',
            'required': False
        },
        {
            'name': 'needIndex',
            'type': 'string',
            'description': '是否需要返回指数数据，比如穿衣指数、紫外线指数等。1为返回，0为不返回。',
            'required': False
        },
        {
            'name': 'need3HourForcast',
            'type': 'string',
            'description': '是否需要每小时数据的累积数组。由于本系统是半小时刷一次实时状态，因此实时数组最大长度为48。每天0点长度初始化为0. 1为需要 0为不',
            'required': False
        },
        {
            'name': 'needAlarm',
            'type': 'string',
            'description': '是否需要天气预警。1为需要，0为不需要。',
            'required': False
        },
        {
            'name': 'needHourData',
            'type': 'string',
            'description': '是否需要每小时数据的累积数组。由于本系统是半小时刷一次实时状态，因此实时数组最大长度为48。每天0点长度初始化为0.',
            'required': False
        },
    ]

    def call(self, params: Union[str, dict], files: List[str] = None, **kwargs) -> str:
        super().call(params=params, files=files)
        params = self._verify_json_format_args(params)

        area = urllib.parse.quote(params['area'].strip())
        needMoreDay = str(params.get('needMoreDay', 0)).strip()
        needIndex = str(params.get('needIndex', 0)).strip()
        needHourData = str(params.get('needHourData', 0)).strip()
        need3HourForcast = str(params.get('need3HourForcast', 0)).strip()
        needAlarm = str(params.get('needAlarm', 0)).strip()

        host = 'https://ali-weather.showapi.com'
        path = '/spot-to-weather'
        appcode = os.environ['AppCode_Area2Weather']  # 开通服务后 买家中心-查看AppCode
        querys = f'area={area}&needMoreDay={needMoreDay}&needIndex={needIndex}&needHourData={needHourData}&need3HourForcast={need3HourForcast}&needAlarm={needAlarm}'
        url = host + path + '?' + querys

        request = urllib.request.Request(url)
        request.add_header('Authorization', 'APPCODE ' + appcode)
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        response = urllib.request.urlopen(request, context=ctx)
        byte_string = response.read()
        content = byte_string.decode('utf-8')
        return content


@register_tool('weather_hour24')
class WeatherHour24(BaseToolWithFileAccess):
    API_URL = 'https://market.aliyun.com/apimarket/detail/cmapi010812#sku=yuncode4812000017'
    description = '查询24小时预报，调用此API查询24小时内具体时间的天气'
    parameters = [
        {
            'name': 'area',
            'type': 'string',
            'description': '地区名称',
            'required': True
        },
    ]

    def call(self, params: Union[str, dict], files: List[str] = None, **kwargs) -> str:
        super().call(params=params, files=files)
        params = self._verify_json_format_args(params)

        area = urllib.parse.quote(params['area'].strip())

        host = 'https://ali-weather.showapi.com'
        path = '/hour24'
        appcode = os.environ('AppCode_weather_hour24')  # 开通服务后 买家中心-查看AppCode
        querys = f'area={area}&areaCode='
        url = host + path + '?' + querys

        request = urllib.request.Request(url)
        request.add_header('Authorization', 'APPCODE ' + appcode)
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        response = urllib.request.urlopen(request, context=ctx)
        byte_string = response.read()
        content = byte_string.decode('utf-8')
        return content


@register_tool('crop_and_resize')
class CropResize(BaseToolWithFileAccess):
    description = '这是一个放大镜功能，截取局部图像并放大从而查看更多细节，如果你无法直接看清细节时可以调用'
    parameters = [
        {
            'name': 'image',
            'type': 'string',
            'description': '输入图片本地路径或URL',
            'required': True
        },
        {
            'name': 'rectangle',
            'type': 'string',
            'description': '需要截取的局部图像区域，使用左上角坐标和右下角坐标表示（原点在图像左上角、向右为x轴正方向、向下为y轴正方向），格式：(x1,y1),(x2,y2)',
            'required': True
        },
    ]

    def _extract_coordinates(self, text):
        pattern = r'\((\d+),\s*(\d+)\)'
        matches = re.findall(pattern, text)
        coordinates = [(int(x), int(y)) for x, y in matches]
        if len(coordinates) >= 2:
            x1, y1 = coordinates[0]
            x2, y2 = coordinates[1]
            return x1, y1, x2, y2

        pattern = r'\((\d+),\s*(\d+),\s*(\d+),\s*(\d+)\)'
        matches = re.findall(pattern, text)
        coordinates = [(int(x1), int(y1), int(x2), int(y2)) for x1, y1, x2, y2 in matches]
        x1, y1, x2, y2 = coordinates[0]
        return coordinates[0]

    def _expand_box(self, x1, y1, x2, y2, factor=1):
        xc = (x1 + x2) / 2
        yc = (y1 + y2) / 2
        w = x2 - x1
        h = y2 - y1
        w_new = w * factor
        h_new = h * factor
        return xc - w_new / 2, yc - h_new / 2, xc + w_new / 2, yc + h_new / 2

    def call(self, params: Union[str, dict], files: List[str] = None, **kwargs) -> List[ContentItem]:
        super().call(params=params, files=files)
        params = self._verify_json_format_args(params)

        image_arg = params['image']  # local path or url
        rectangle = params['rectangle']

        # open image
        if image_arg.startswith('http'):
            response = requests.get(image_arg)
            response.raise_for_status()
            image = Image.open(BytesIO(response.content))
        elif os.path.exists(image_arg):
            image = Image.open(image_arg)
        else:
            image = Image.open(os.path.join(self.work_dir, image_arg))

        coordinates = self._extract_coordinates(rectangle)
        x1, y1, x2, y2 = self._expand_box(*coordinates, factor=1.35)

        w, h = image.size
        x1, y1 = round(x1 / 1000 * w), round(y1 / 1000 * h)
        x2, y2 = round(x2 / 1000 * w), round(y2 / 1000 * h)

        # remove padding
        x1, y1, x2, y2 = max(x1, 0), max(y1, 0), min(x2, w), min(y2, h)

        cropped_image = image.crop((x1, y1, x2, y2))

        # save
        output_path = os.path.abspath(os.path.join(self.work_dir, f'{uuid.uuid4()}.png'))
        cropped_image.save(output_path)

        return [
            ContentItem(image=output_path),
            ContentItem(text=f'（ 这张放大的局部区域的图片的URL是 {output_path} ）'),
        ]


def init_agent_service():
    llm_cfg_vl = {
        # Using Qwen2-VL deployed at any openai-compatible service such as vLLM:
        # 'model_type': 'qwenvl_oai',
        # 'model': 'Qwen2-VL-7B-Instruct',
        # 'model_server': 'http://localhost:8000/v1',  # api_base
        # 'api_key': 'EMPTY',

        # Using Qwen2-VL provided by Alibaba Cloud DashScope's openai-compatible service:
        # 'model_type': 'qwenvl_oai',
        # 'model': 'qwen-vl-max-0809',
        # 'model_server': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        # 'api_key': os.getenv('DASHSCOPE_API_KEY'),

        # Using Qwen2-VL provided by Alibaba Cloud DashScope:
        'model_type': 'qwenvl_dashscope',
        'model': 'qwen-vl-max-0809',
        'api_key': os.getenv('DASHSCOPE_API_KEY'),
        'generate_cfg': dict(max_retries=10,)
    }

    tools = [
        'crop_and_resize',
        'code_interpreter',
    ]  # code_interpreter is a built-in tool in Qwen-Agent

    # API tools
    if 'AppCode_WeatherHour24' in os.environ:
        tools.append('express_tracking')
    else:
        print(f'Please get AppCode from {WeatherHour24.API_URL} and execute:\nexport AppCode_WeatherHour24=xxx')
        print('express_tracking is disabled!')

    if 'AppCode_Area2Weather' in os.environ:
        tools.append('weather_hour24')
    else:
        print(f'Please get AppCode from {Area2Weather.API_URL} and execute:\nexport AppCode_Area2Weather=xxx')
        print('weather_hour24 is disabled!')

    if 'AppCode_ExpressTracking' in os.environ:
        tools.append('area_to_weather')
    else:
        print(f'Please get AppCode from {ExpressTracking.API_URL} and execute:\nexport AppCode_ExpressTracking=xxx')
        print('area_to_weather is disabled!')

    bot = FnCallAgent(
        llm=llm_cfg_vl,
        name='Qwen2-VL',
        description='function calling',
        function_list=tools,
    )

    return bot


def test():
    # Define the agent
    bot = init_agent_service()

    # Chat
    messages = [{
        'role':
            'user',
        'content': [
            {
                'image': os.path.abspath(os.path.join(ROOT_RESOURCE, 'screenshot_with_plot.jpeg'))
            },
            {
                'text': '调用工具放大右边的表格'
            },
        ],
    }]

    for response in bot.run(messages=messages):
        print('bot response:', response)


def app_gui():
    # Define the agent
    bot = init_agent_service()

    WebUI(bot).run()


if __name__ == '__main__':
    test()
    # app_gui()
