# 接口自动化的装饰器

import allure
import functools
import json
import pytest
import time
from requests.models import Response

from enums.api_enum import MethodEnum
from exceptions import PytestAutoTestError
from exceptions.error_msg import ERROR_MSG_0350
from models.api_model import ApiDataModel, ResponseModel, ApiTestCaseModel, RequestModel, ApiInfoModel
from settings.settings import PRINT_EXECUTION_RESULTS, REQUEST_TIMEOUT_FAILURE_TIME
from tools.log import log


def case_data(case_id: int | list[int] | None = None, case_name: str | list[str] | None = None):
    def decorator(func):
        log.debug(f'开始查询用例,用例ID:{case_id} 用例名称:{case_name}')
        from sources import SourcesData
        if case_id:
            test_case_list = SourcesData.get_api_test_case(is_dict=False, id=case_id)
        elif case_name:
            test_case_list = SourcesData.get_api_test_case(is_dict=False, name=case_name)
        else:
            raise PytestAutoTestError(*ERROR_MSG_0350)

        @pytest.mark.flaky(reruns=3)
        @pytest.mark.parametrize("test_case", test_case_list)
        def wrapper(self, test_case):
            test_case_model = ApiTestCaseModel.get_obj(test_case)
            log.debug(f'准备开始执行用例,数据:{test_case_model.model_dump_json()}')
            allure.dynamic.title(test_case.get('name'))
            allure.attach(json.dumps(test_case, ensure_ascii=False), '用例数据')

            data = ApiDataModel(base_data=self.data_model.base_data,
                                test_case=test_case_model)
            try:
                func(self, data=data)
                self.ass_main(data)
                # allure.attach(self.test_data.get_all(), '缓存数据')
            except PytestAutoTestError as error:
                log.error(error.msg)
                allure.attach(error.msg, '发生已知异常')
                raise error

        return wrapper

    return decorator


def request_data(api_info_id):
    """
    处理请求的数据和结果,写入allure报告
    :return:
    """

    def decorator(func):

        # @functools.wraps(func)
        def wrapper(*args, **kwargs) -> ApiDataModel:
            log.debug(f'开始查询接口数据,ID:{api_info_id}')
            data: ApiDataModel = kwargs.get('data')
            if len(args) == 2:
                data: ApiDataModel = args[1]
            from sources import SourcesData
            api_info_dict = SourcesData.get_api_info(id=api_info_id)
            api_info_model = ApiInfoModel.get_obj(api_info_dict)
            log.debug(f'查询到接口的数据,接口ID:{api_info_model.model_dump_json()}')
            data.request = RequestModel(
                url=api_info_model.url,
                method=MethodEnum.get_value(api_info_model.method),
                headers=api_info_model.headers if api_info_model.headers else data.base_data.headers,
                params=data.test_case.params,
                data=data.test_case.data,
                json_data=data.test_case.json_data if data.test_case.json_data else api_info_model.json_data,
                file=data.test_case.file,
            )
            log.debug(f'默认准备好的请求,数据:{data.request.model_dump_json()}')
            res_args = func(*args, **kwargs)
            allure.attach(str(data.request.url), 'URL')
            allure.attach(str(data.request.method), '请求方法')
            allure.attach(str(data.request.headers), '请求头')
            if data.request.params:
                allure.attach(json.dumps(data.request.params, ensure_ascii=False), '参数')
            if data.request.data:
                allure.attach(json.dumps(data.request.data, ensure_ascii=False), '表单')
            if data.request.json_data:
                allure.attach(json.dumps(data.request.json_data, ensure_ascii=False), 'JSON')
            if data.request.file:
                allure.attach(str(data.request.file), '文件')
            allure.attach(str(data.response.status_code), '响应状态码')
            allure.attach(str(data.response.response_time * 1000), '响应时间(毫秒)')
            allure.attach(json.dumps(data.response.response_dict, ensure_ascii=False), '响应结果')
            return res_args

        return wrapper

    return decorator


def timer(func):
    """
    封装统计函数执行时间装饰器
    :return:
    """

    @functools.wraps(func)
    def swapper(*args, **kwargs) -> ResponseModel:
        start = time.time()
        response: Response = func(*args, **kwargs)
        response_time = time.time() - start
        if response_time > REQUEST_TIMEOUT_FAILURE_TIME:
            log.error(
                f"\n{'=' * 100}\n"
                f"测试用例执行时间较长,请关注.\n"
                f"函数运行时间: {response_time} ms\n"
                f"测试用例相关数据: {response}\n"
                f"{'=' * 100}")
        try:
            response_dict = response.json()
        except json.JSONDecodeError as error:
            response_dict = {'error_msg': '您可以检查返回的值是否是json,如果不是,就不要使用response_dict',
                             'error': str(error)}
        formatted_response = ''.join(response.text.split())
        log.debug(f'请求的结果,response:{formatted_response}')
        data: RequestModel = args[1]
        return ResponseModel(
            url=response.url,
            status_code=response.status_code,
            method=data.method,
            headers=response.headers,
            response_text=formatted_response,
            response_dict=response_dict,
            response_time=response_time,
            content=response.content
        )

    return swapper


def log_decorator(func):
    """
    封装日志装饰器, 打印请求信息
    :return:
    """

    @functools.wraps(func)
    def swapper(*args, **kwargs) -> ApiDataModel:
        data = func(*args, **kwargs)
        log.debug(f'用例执行完成,整个响应体:{data.response.model_dump()}')
        if PRINT_EXECUTION_RESULTS:
            _log_msg = f"\n{'=' * 200}\n" \
                       f"用例标题: {data.test_case.name}\n" \
                       f"请求路径: {data.response.url}\n" \
                       f"请求方式: {data.response.method}\n" \
                       f"请 求 头:  {data.request.headers}\n"
            if data.request.params is not None:
                _log_msg += f"请求params:{data.request.params}\n"
            if data.request.data is not None:
                _log_msg += f"请求data:{data.request.data}\n"
            if data.request.json_data is not None:
                _log_msg += f"请求json:{data.request.json_data}\n"
            if data.request.file is not None:
                _log_msg += f"请求文件:{data.request.file}\n"
            _log_msg += f"Http状态码: {data.response.status_code}\n" \
                        f"接口响应时长: {data.response.response_time} ms\n" \
                        f"接口响应内容: {data.response.response_text}\n" \
                        f"{'=' * 200}"
            if data.response.status_code == 200 or data.response.status_code == 300:
                log.info(_log_msg)
            else:
                log.error(_log_msg)
        return data

    return swapper

# UI自动化的装饰器

import allure
import pytest

from enums.ui_enum import BrowserTypeEnum
from exceptions import PytestAutoTestError
from exceptions.error_msg import ERROR_MSG_0350
from models.ui_model import WEBConfigModel, UiDataModel, UiTestCaseModel
from tools.base_page.web.new_browser import NewBrowser
from tools.log import log


def case_data(case_id: int | list[int] | None = None, case_name: str | list[str] | None = None):
    def decorator(func):
        log.debug(f'开始查询用例,用例ID:{case_id} 用例名称:{case_name}')
        from sources import SourcesData
        if case_id:
            test_case_list = SourcesData.get_ui_test_case(is_dict=False, id=case_id)
        elif case_name:
            test_case_list = SourcesData.get_ui_test_case(is_dict=False, name=case_name)
        else:
            raise PytestAutoTestError(*ERROR_MSG_0350)

        @pytest.mark.parametrize("test_case", test_case_list)
        def wrapper(self, test_case):
            allure.dynamic.title(test_case.get('name'))
            browser = NewBrowser(WEBConfigModel(browser_type=BrowserTypeEnum.CHROMIUM))
            context, page = browser.new_context_page()
            data = UiDataModel(base_data=self.data_model.base_data, test_case=UiTestCaseModel.get_obj(test_case))
            func(self, execution_context=(context, page), data=data)
            context.close()
            page.close()

        return wrapper

    return decorator