import re
import textwrap
import random
import string
from datetime import datetime
from django.conf import settings
import base64
import requests
from io import BytesIO
from rest_framework.serializers import ValidationError
import ast
from typing import Dict
from django.core.serializers.json import DjangoJSONEncoder
from decimal import Decimal

class MyJSONEncoder(DjangoJSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super().default(obj)

class CodeAnalyzer(ast.NodeVisitor):
    def __init__(self):
        self.errors = []

    def analyze(self, code):
        try:
            tree = ast.parse(code)
            self.visit(tree)
        except SyntaxError as e:
            self.errors.append(f"Syntax error at line {e.lineno}: {e.msg}")

    def visit_Import(self, node):
        self.errors.append(f"Forbidden import statement at line {node.lineno}")
        self.generic_visit(node)

    def visit_ImportFrom(self, node):
        self.errors.append(f"Forbidden import-from statement at line {node.lineno}")
        self.generic_visit(node)

    def visit_Exec(self, node):
        self.errors.append(f"Forbidden exec statement at line {node.lineno}")
        self.generic_visit(node)

    def visit_Call(self, node):
        # Detect forbidden function calls like eval, open, etc.
        if isinstance(node.func, ast.Name):
            if node.func.id in ['eval', 'exec', 'open', 'compile']:
                self.errors.append(f"Forbidden function '{node.func.id}' used at line {node.lineno}")
        self.generic_visit(node)

    def visit_With(self, node):
        # Detect forbidden with-open blocks
        for item in node.items:
            context_expr = item.context_expr
            # For Python versions >= 3.9, context_expr is replaced by context_exprs
            if isinstance(context_expr, ast.Call) and isinstance(context_expr.func, ast.Name):
                if context_expr.func.id == 'open':
                    self.errors.append(f"Forbidden 'open' statement within 'with' block at line {node.lineno}")
        self.generic_visit(node)

def is_close(num1, num2=0, tolerance=1e-9):
    """
    Check if a numeric value (int, float, etc.) is close.
    """
    if isinstance(num1, float) or isinstance(num2, float):     # Float check
        return abs(num1-num2) < tolerance
    elif isinstance(num1, int) and isinstance(num2, int):         # Integer check
        return num1 == num2
    else:
        raise ValueError("Unsupported numeric type")

def tran64(s):
    missing_padding = len(s) % 4
    if missing_padding != 0:
        s = s+'=' * (4 - missing_padding)
    return s


def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner


def print_roundtrip(response, *args, **kwargs):
    def format_headers(d): return '\n'.join(f'{k}: {v}' for k, v in d.items())
    print(textwrap.dedent('''
        ---------------- request ----------------
        {req.method} {req.url}
        {reqhdrs}

        {req.body}
        ---------------- response ----------------
        {res.status_code} {res.reason} {res.url}
        {reshdrs}

        {res.text}
    ''').format(
        req=response.request,
        res=response,
        reqhdrs=format_headers(response.request.headers),
        reshdrs=format_headers(response.headers),
    ))


def ranstr(num):
    salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num))
    return salt


def rannum(num):
    salt = ''.join(random.sample(string.digits, num))
    return salt


def timestamp_to_time(millis):
    """10位时间戳转换为日期格式字符串"""
    return datetime.fromtimestamp(millis)


def convert_to_base64(path: str):
    """给定图片转base64

    Args:
        path (str): 图片地址
    """
    if path.startswith('http'):  # 如果是网络图片
        return str(base64.b64encode(BytesIO(requests.get(url=path).content).read()), 'utf-8')
    else:
        with open(settings.BASE_DIR + path, 'rb') as f:
            return str(base64.b64encode(f.read()), 'utf-8')


def p_in_poly(p, poly):
    px = p['x']
    py = p['y']
    flag = False

    i = 0
    l = len(poly)
    j = l - 1
    # for(i = 0, l = poly.length, j = l - 1; i < l; j = i, i++):
    while i < l:
        sx = poly[i]['x']
        sy = poly[i]['y']
        tx = poly[j]['x']
        ty = poly[j]['y']

        # 点与多边形顶点重合
        if (sx == px and sy == py) or (tx == px and ty == py):
            return (px, py)

        # 判断线段两端点是否在射线两侧
        if (sy < py and ty >= py) or (sy >= py and ty < py):
            # 线段上与射线 Y 坐标相同的点的 X 坐标
            x = sx + (py - sy) * (tx - sx) / (ty - sy)
            # 点在多边形的边上
            if x == px:
                return (px, py)
            # 射线穿过多边形的边界
            if x > px:
                flag = not flag
        j = i
        i += 1

    # 射线穿过多边形边界的次数为奇数时点在多边形内
    return (px, py) if flag else 'out'


def check_id_number_e(val):
    re_s = r'^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$'
    if not re.match(re_s, val):
        raise ValidationError('身份证号校验错误')
    return val


def get_info_from_id(val):
    birth = val[6:14]
    birth_year = birth[0:4]
    age = datetime.now().year - int(birth_year)
    sex = int(val[-2])
    gender = '女'
    if sex % 2:
        gender = '男'
    return dict(age=age, gender=gender)


def check_id_number(idcard):
    """校验身份证号

    Args:
        id_number (_type_): 身份证号
    """
    Errors = ['身份证号码位数不对!', '身份证号码出生日期超出范围或含有非法字符!', '身份证号码校验错误!', '身份证地区非法!']
    area = {"11": "北京", "12": "天津", "13": "河北", "14": "山西", "15": "内蒙古", "21": "辽宁", "22": "吉林", "23": "黑龙江",
            "31": "上海", "32": "江苏", "33": "浙江", "34": "安徽", "35": "福建", "36": "江西", "37": "山东", "41": "河南", "42": "湖北",
            "43": "湖南", "44": "广东", "45": "广西", "46": "海南", "50": "重庆", "51": "四川", "52": "贵州", "53": "云南", "54": "西藏",
            "61": "陕西", "62": "甘肃", "63": "青海", "64": "宁夏", "65": "新疆", "71": "台湾", "81": "香港", "82": "澳门", "91": "国外"}
    idcard = str(idcard)
    idcard = idcard.strip()
    idcard_list = list(idcard)

    # 地区校验
    if str(idcard[0:2]) not in area:
        return False, Errors[3]

    # 15位身份号码检测
    if len(idcard) == 15:
        if ((int(idcard[6:8]) + 1900) % 4 == 0 or (
                (int(idcard[6:8]) + 1900) % 100 == 0 and (int(idcard[6:8]) + 1900) % 4 == 0)):
            ereg = re.compile(
                '[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$')  # //测试出生日期的合法性
        else:
            ereg = re.compile(
                '[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$')  # //测试出生日期的合法性
        if re.match(ereg, idcard):
            return True, ''
        else:
            return False, Errors[1]
    # 18位身份号码检测
    elif len(idcard) == 18:
        # 出生日期的合法性检查
        # 闰年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))
        # 平年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))
        if (int(idcard[6:10]) % 4 == 0 or (int(idcard[6:10]) % 100 == 0 and int(idcard[6:10]) % 4 == 0)):
            # 闰年出生日期的合法性正则表达式
            ereg = re.compile(
                '[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$')
        else:
            # 平年出生日期的合法性正则表达式
            ereg = re.compile(
                '[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$')
        # 测试出生日期的合法性
        if re.match(ereg, idcard):
            # 计算校验位
            S = (int(idcard_list[0]) + int(idcard_list[10])) * 7 + (int(idcard_list[1]) + int(idcard_list[11])) * 9 + (
                int(idcard_list[2]) + int(idcard_list[12])) * 10 + (
                int(idcard_list[3]) + int(idcard_list[13])) * 5 + (
                int(idcard_list[4]) + int(idcard_list[14])) * 8 + (
                int(idcard_list[5]) + int(idcard_list[15])) * 4 + (
                int(idcard_list[6]) + int(idcard_list[16])) * 2 + int(idcard_list[7]) * 1 + int(
                idcard_list[8]) * 6 + int(idcard_list[9]) * 3
            Y = S % 11
            M = "F"
            JYM = "10X98765432"
            M = JYM[Y]  # 判断校验位
            if M == idcard_list[17]:  # 检测ID的校验位
                return True, ''
            else:
                return False, Errors[2]
        else:
            return False, Errors[1]
    else:
        return False, Errors[0]


def check_id_number_strict(id_card: str):
    """校验 18 位中国身份证号码是否合法"""
    if not re.match(r'^\d{17}[\dXx]$', id_card):
        return False

    # 系数和校验码
    weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    check_codes = "10X98765432"

    # 计算校验码
    total = sum(int(id_card[i]) * weights[i] for i in range(17))
    expected_check_code = check_codes[total % 11]

    if expected_check_code == id_card[-1].upper():
        return id_card
    raise ValidationError('身份证号校验失败')

def check_phone_e(phone):
    re_phone = r'^1\d{10}$'
    if not re.match(re_phone, phone):
        raise ValidationError('手机号格式错误')
    return phone


def compare_dicts(dict1, dict2, ignore_order=False):
    if ignore_order:
        for key in sorted(dict1.keys()):
            if key not in dict2 or not compare_values(dict1[key], dict2[key], ignore_order):
                return False
        return True
    else:
        return dict1 == dict2


def compare_lists_of_dicts(list1, list2, ignore_order=False):
    """比较两个列表，这里的列表包含字典（对象）"""
    if ignore_order:
        # 转换列表中的字典为元组列表，然后排序进行比较
        sorted_list1 = sorted((tuple(sorted(d.items())) for d in list1))
        sorted_list2 = sorted((tuple(sorted(d.items())) for d in list2))
        return sorted_list1 == sorted_list2
    else:
        # 按顺序比较列表中的字典
        return all(compare_dicts(obj1, obj2) for obj1, obj2 in zip(list1, list2))


def compare_values(val1, val2, ignore_order=False):
    """通用比较函数，也可以处理字典和列表。"""
    if isinstance(val1, list) and isinstance(val2, list):
        # 假设这里我们关心列表中对象的顺序
        return compare_lists_of_dicts(val1, val2, ignore_order)
    elif isinstance(val1, dict) and isinstance(val2, dict):
        return compare_dicts(val1, val2, ignore_order)
    else:
        return val1 == val2


def build_tree_from_list(data, parent_field="parent"):
    id_map = {item["id"]: item for item in data}
    tree = []

    for item in data:
        parent_id = item.get(parent_field, None)
        if parent_id is None:
            tree.append(item)
        else:
            parent = id_map.get(parent_id, None)
            if parent:
                parent.setdefault("children", []).append(item)
            else:
                tree.append(item)

    return tree

def convert_ordereddict(item):
    """递归地将 OrderedDict 转换为普通字典"""
    if isinstance(item, list):
        return [convert_ordereddict(i) for i in item]  # 递归处理列表中的每个元素
    elif isinstance(item, dict):
        # 如果是 OrderedDict 或普通字典，遍历所有键值对进行转换
        return {key: convert_ordereddict(value) for key, value in item.items()}
    return item 

def update_dict(d, update_data:Dict) -> Dict:
    d.update(update_data)
    return d
