
from app.core.crud import CRUDBase
from app.schemas.weixin import (
    WeixinCustomerCreate,
    WeixinCustomerUpdate,
    WeixinCustomerXingyunCreate,
)
from typing import List

from app.models.weixin import WeixinCustomer, WeixinUser, CustomerGroup
from app.schemas.weixin import WeixinUserBindInfo, WeixinOrderBindInfo

from some_sdk.services.binder import lintao_client, xy_client, wk_client
from some_sdk.lintao_sdk.biz.by_order import get_order_relative_user, save_other_memo
from some_sdk.xingyun_sdk.apis.customer import bind_user_to_xingyun, list_user_order
from some_sdk.xingyun_sdk.apis.work_external_contact import list_all_contact_by_addtime, list_all_contact_by_addtime_and_tag
from some_sdk.wk_weixin_sdk.apis.corp_user import from_service_external_userid
from some_sdk.wk_weixin_sdk.apis.extern_user import get_external_user_chat_info

from app.controllers.msg import msg_controller
from app.schemas.msg import MsgType

from app.utils.common import async_split_generator
import pickle
import hashlib

import re, os
from datetime import datetime, timedelta
from app.core.cache import cache_if

import logging
logger = logging.getLogger(__name__)

EXCLUDE_SHOP_NAMES = ['领淘文具旗舰店']
XINGYUN_USE_TIME = datetime(2025, 6, 18)

class WeixinCustomerController(CRUDBase[WeixinCustomer, WeixinCustomerCreate, WeixinCustomerUpdate]):
    def __init__(self):
        super().__init__(model=WeixinCustomer)
    
    async def update_from_group(self, external_members: list[dict]):
        # 同步更新数据库
        member_dict = {}
        for member in external_members:
            member = member.copy()
            userid = member.pop('userid', None)

            weixin_name = member.pop('name', None)
            weixin_unionid = member.pop('unionid', None)

            member_info = WeixinCustomer.orm_format(member)
            member_info['weixin_id'] = userid
            member_info['weixin_name'] = weixin_name
            member_info['weixin_unionid'] = weixin_unionid
            member_dict[userid] = member_info

        member_list = await self.model.filter(weixin_id__in=member_dict.keys())

        update_list = []
        update_fileds = ['weixin_unionid', 'weixin_id', 'weixin_name', 'order_id', 'taobao_id', 'taobao_name', 'extra', 'need_confirm']

        history = {}
        for member in member_list:
            member_info = member_dict.pop(member.weixin_id, history.get(member.weixin_id, {}))
            if not member_info: continue
            history[member.weixin_id] = member_info

            member_info['extra'] = {**(member.extra or {}), **member_info['extra']}

            updated = {}
            for field in update_fileds:
                old_value = getattr(member, field)
                if field not in member_info:
                    continue
                value = member_info.get(field)
                if old_value != value:
                    setattr(member, field, value)
                    updated[f'{field}:{str(old_value)}'] = value
            if updated:
                logger.info(f'用户 {member.weixin_name} 更新关系为 {updated}')
                update_list.append(member)
        if update_list:
            await self.model.bulk_update(update_list, fields=update_fileds)

        # 新增用户
        create_list = []
        for userid, member_info in member_dict.items():
            create_list.append(WeixinCustomer(
                **member_info,
            ))
        if create_list:
            await self.model.bulk_create(create_list)
        
        return {
            'update_count': len(update_list),
            'create_count': len(create_list),
        }

    async def bind_user(self, user_in: WeixinUserBindInfo):
        if not user_in.taobao_id:
            raise ValueError("淘宝ID不能为空")
        await self.model.filter(taobao_id=user_in.taobao_id).update_from_dict(user_in.model_dump(exclude_unset=True))
    
    async def remark_order(self, order: dict, remark: str):
        ctid = order.get("ctid") or order.get("trade_no")
        # if remark in (order.get("remark") or ''):
        if '企微' in (order.get("remark") or ''):
            logger.info(f'订单 {ctid} 备注中已包含 企微 备注，无需重复添加')
            return
        remark_list = [order.get("remark", ""), remark]
        append_remark = '；'.join(filter(lambda x: x, remark_list))

        if os.getenv("APP_ENV") == "prod":
            result = await save_other_memo(lintao_client, order_id=ctid, other_memo=append_remark)
            assert result["type"] == "success", f"保存备注失败，响应: {result}"
            logger.info(f'成功为订单 {ctid} 打上备注 {append_remark}')
        else:
            logger.info(f'非生产环境，不实际为订单 {ctid} 打上备注 {append_remark}')

    async def remark_order_by_order_id(self, remark: str, order_id: str=None, orders: list=None):
        if not orders:
            assert order_id, "订单ID不能为空"
            orders = [o async for o in get_order_relative_user(lintao_client, trade_no=order_id)]
            assert orders, f"订单 {order_id} 不存在"
        
        # 打上备注
        logger.info(f'即将为以下订单 {[o.get("trade_no") for o in orders]} 打上备注 {remark}')
        need_monitor_order_list = []
        for order in orders:
            if not order.get('title'):
                # 未领单的订单，需要添加到监控列表中进行监控，防止被刷掉
                need_monitor_order_list.append(order)
                logger.info(f'订单 {order.get("trade_no")} 未领单，需要添加到监控列表中进行监控，防止被刷掉')

            try:
                await self.remark_order(order, remark=remark)
            except Exception as e:
                logger.error(f'为订单 {order.get("trade_no")} 打上备注 {remark} 失败，异常: {e}')
                need_monitor_order_list.append(order)
        
        return need_monitor_order_list


    async def bind_order(self, bind_in: WeixinOrderBindInfo):
        orders = [o async for o in get_order_relative_user(lintao_client, trade_no=bind_in.order_id)]
        assert orders, f"订单 {bind_in.order_id} 不存在"

        orders = [o for o in orders if o.get('trade_no') == bind_in.order_id]
        order = orders[0]

        buyer_ids = []
        for user in order.get('users', []):
            taobao_id = user.get('id', '')
            if not taobao_id or re.match(r'^\d+$', taobao_id): continue
            buyer_ids.append(taobao_id)

        # 更新还是新建？
        orms = await self.model.filter(weixin_id=bind_in.userid).all()
        logger.info(f'用户 {bind_in.userid} 准备绑定订单 {len(orms)} {[orm.weixin_name for orm in orms]}')

        update_list = []
        for orm in orms:
            if orm.order_id and orm.order_id != order.get('trade_no'):
                logger.warning(f'用户 {orm.weixin_name} 已绑定订单 {orm.order_id}，即将覆盖为 {order.get("trade_no")}')

            orm.weixin_id = bind_in.userid
            orm.order_id = order.get('trade_no')
            orm.shop_name = order.get('shop_name', '')
            orm.taobao_id = user.get('id', '')
            orm.taobao_name = user.get('name', '')
            update_list.append(orm)
        
        if update_list:
            logger.info(f'更新用户 {bind_in.userid} 绑定订单 {order.get("trade_no")} 中的用户 {user}')
            await self.model.bulk_update(update_list, fields=['order_id', 'weixin_id', 'taobao_id', 'taobao_name'])
            return True, orders
        
        if not orms:
            logger.info(f'绑定用户 {bind_in.userid} 绑定订单 {order.get("trade_no")} 中的用户 {user}')
            orm_user = WeixinCustomer(
                weixin_id=bind_in.userid,
                order_id=order.get('trade_no'),
                taobao_id=user.get('id', ''),
                taobao_name=user.get('name', ''),
            )
            await orm_user.save()
            return True, orders

        return False, orders
    
    async def bind_xingyun_user(self, contact_orm: WeixinCustomer):
        assert contact_orm.xingyun_id, "星云用户ID不能为空"
        if contact_orm.shop_name in EXCLUDE_SHOP_NAMES:
            contact_orm.xingyun_sync = True
            contact_orm.extra = contact_orm.extra or {}
            contact_orm.extra['system_remark'] = f'店铺名称: {contact_orm.shop_name}，没有开通星云有客服务，故此跳过。'
            return

        order_id = contact_orm.order_id.split('_')[-1]
        logger.info(f'同步客户订单信息到星云: {contact_orm.xingyun_id} -》 {order_id}')
        try:
            await bind_user_to_xingyun(xy_client, contact_orm.xingyun_id, order_id)
        except:
            logger.error(f'同步客户订单信息到星云失败: {contact_orm.xingyun_id} -》 {order_id}, 尝试寻找同用户的其他订单来绑定')
            shop_name = ''
            async for order in get_order_relative_user(lintao_client, buyer_nick=contact_orm.taobao_name):
                shop_name = order.get('shop_name', '')
                if shop_name in EXCLUDE_SHOP_NAMES:
                    continue

                if not order.get('create_time'): continue
                create_time = order.get('create_time')
                create_time = datetime.fromisoformat(create_time.replace("Z", "+00:00"))
                if create_time < XINGYUN_USE_TIME:
                    continue

                order_id = order.get('trade_no').split('_')[-1]
                await bind_user_to_xingyun(xy_client, contact_orm.xingyun_id, order_id)
                logger.info(f'通过同用户的其他订单来同步客户订单信息到星云成功: {contact_orm.xingyun_id} -》 {order_id}')
                break

            if shop_name and not contact_orm.shop_name:
                contact_orm.shop_name = order.get('shop_name', '')
                await contact_orm.save()

        contact_orm.xingyun_sync = True

    async def load_all_user_from_xingyun(self, task_id: str, add_time_start: datetime, add_time_end: datetime):
        logger.info(f'从星云加载客户数据，时间范围 {task_id} {add_time_start} - {add_time_end}')
        contact_iter = list_all_contact_by_addtime_and_tag(xy_client, task_id=task_id, keyword='', add_time_start=add_time_start, add_time_end=add_time_end)
        
        return await self._load_user_from_xingyun(contact_iter, eager_load=True)

    async def load_user_from_xingyun(self, add_time_start: datetime, add_time_end: datetime):
        logger.info(f'从星云加载客户数据，时间范围 {add_time_start} - {add_time_end}')
        contact_iter = list_all_contact_by_addtime(xy_client, keyword='', add_time_start=add_time_start, add_time_end=add_time_end)
        
        return await self._load_user_from_xingyun(contact_iter)

    async def _load_user_from_xingyun(self, contact_iter: list, eager_load: bool = False):
        logger.info(f'从星云加载客户数据，开始同步')
        load_result = {"update_count": 0, "add_count": 0}
        async for contact_list in async_split_generator(contact_iter, 10):
            try: result = await self.save_xingyun_contact_info(contact_list)
            except Exception as e:
                logger.error(f'从星云加载客户数据，同步失败 {e}')
                logger.exception(e)
                raise e

            # 全部都已经同步完了
            if not eager_load and result.get("update_count", 0) + result.get("add_count", 0) == 0:
                logger.info(f'从星云加载客户数据，全部同步完成')
                break

            load_result['update_count'] += result.get("update_count", 0)
            load_result['add_count'] += result.get("add_count", 0)
            logger.info(f'从星云加载客户数据{eager_load}，更新 {result.get("update_count", 0)} 条，新增 {result.get("add_count", 0)} 条')
        logger.info(f'从星云加载客户数据，总 {load_result["update_count"] + load_result["add_count"]} 条，更新 {load_result["update_count"]} 条，新增 {load_result["add_count"]} 条')
        await self.sync_xingyun_contact_info()

        return load_result
    
    async def save_xingyun_contact_info(self, contact_list: list):
        contact_dict = {contact.get('cid'): contact for contact in contact_list}
        contact_orm_list: List[WeixinCustomer] = await self.model.filter(xingyun_id__in=contact_dict.keys())

        field_names = set()
        update_list = []

        async def update(contact_dict, contact_orm_list, key):
            history = {}
            for contact_orm  in contact_orm_list:
                value = getattr(contact_orm, key)
                contact = contact_dict.pop(value, history.get(value, {}))
                if not contact: continue
                history[value] = contact

                try:
                    contact_in = WeixinCustomerXingyunCreate(**contact).transform_to_extra()
                except Exception as e:
                    logger.error(f'客户数据转换错误，chat_id: {contact}, {e}')
                    continue
                # print(type(contact_in), contact_in)
                updated = False

                contact_in_dict = contact_in.model_dump(exclude_unset=True)
                field_names.update(contact_in_dict.keys())

                for field_name in field_names:
                    
                    old_value = getattr(contact_orm, field_name)
                    value = contact_in_dict.get(field_name)
                    if old_value != value:
                        setattr(contact_orm, field_name, value)
                        updated = True
                
                new_extra = {**(contact_orm.extra or {}), **contact_in.extra}
                if contact_in.extra != new_extra:
                    if new_extra != contact_orm.extra:
                        contact_orm.extra = new_extra
                        updated = True
                
                # 触发同步信息到星云
                if updated and not contact_orm.xingyun_sync and contact_orm.xingyun_id:
                    try:
                        await self.bind_xingyun_user(contact_orm)
                        update_list.append(contact_orm)
                    except Exception as e:
                        logger.error(f'客户数据同步错误，xingyun_id: {contact_orm.xingyun_id}, {e}')
                        continue
            
            if update_list:
                await self.model.bulk_update(update_list, list(field_names) + ['extra', 'xingyun_sync'])

        await update(contact_dict, contact_orm_list, 'xingyun_id')

        add_dict = {}
        for contact in contact_dict.values():
            externalUserid = contact.get('externalUserid', None)
            if not externalUserid:
                continue
            try:
                resp = await from_service_external_userid(wk_client, externalUserid)
            except Exception as e:
                logger.error(f'从星云加载客户数据，获取用户ID失败，externalUserid: {externalUserid}, {e}')
                continue
            if not resp or not resp.get('external_userid'):
                continue

            external_userid = resp.get('external_userid')
            contact['external_userid'] = external_userid
            add_dict[external_userid] = contact

        contact_orm_list = await self.model.filter(weixin_id__in=add_dict.keys())
        await update(add_dict, contact_orm_list, 'weixin_id')

        add_list = []
        for contact in add_dict.values():
            contact_in = WeixinCustomerXingyunCreate(**contact).transform_to_extra()
            add_list.append(self.model(**contact_in.model_dump(exclude_unset=True)))
        
        if add_list:
            await self.model.bulk_create(add_list)
        
        return {
            'update_count': len(update_list),
            'add_count': len(add_list),
        }
    
    async def bind_user(self, bind_info: WeixinUserBindInfo):
        return await self.bind_user(bind_info)
    
    async def monitor_erp_order(self):
        # 从领淘ERP加载订单相关用户
        # 获取上一个订单的创建时间
        cache_file = 'env/order_cache.pkl'
        newest_order_create_time = None
        today_end = datetime.now().replace(hour=23, minute=59, second=59, microsecond=0)
        try:
            with open(cache_file, 'rb') as f:
                last_order = pickle.load(f)
                newest_order_create_time = last_order.get('create_time')
                logger.info(f'从缓存文件加载最新订单创建时间，{newest_order_create_time}')
        except FileNotFoundError: 
            newest_order_create_time = today_end.replace(hour=0, minute=0, second=0, microsecond=0)
            newest_order_create_time = newest_order_create_time.strftime('%Y-%m-%d %H:%M:%S')
        
        order_list = [o async for o in get_order_relative_user(lintao_client, start_date=newest_order_create_time, end_date=today_end.strftime('%Y-%m-%d %H:%M:%S'))]
        msg_list, newest_order = await self.find_bind_order(order_list)

        # 保存最新订单到缓存文件
        if newest_order:
            newest_order['create_time'] = newest_order['create_time'].replace('T', ' ')
            with open(cache_file, 'wb') as f:
                pickle.dump(newest_order, f)

        return msg_list
    
    async def find_bind_order(self, order_list: list):
        
        buyer_order_dict = {}
        newest_order = None
        last_erp_id = 0

        order_id_set = set()
        for order in order_list:
            erp_id = order.get('id')
            if erp_id <= 6368700: break

            create_time = order.get('create_time')
            if last_erp_id < erp_id: 
                if create_time: newest_order = order
                last_erp_id = erp_id
            
            order_id = order.get('trade_no')
            if order_id in order_id_set: continue
            order_id_set.add(order_id)

            users = order.get("users", [])
            buyer_id_set = set()
            for user in users:
                role = user.get('role')
                if role != 'buyer': continue
                buyer_id = user.get('id')
                if not buyer_id: continue
                if buyer_id in buyer_id_set: continue
                buyer_id_set.add(buyer_id)

                buyer_order_dict.setdefault(buyer_id, []).append(order)
        
        customer_list = await self.model.filter(taobao_id__in=buyer_order_dict.keys()).all()
        
        # 通知新订单
        # （客户id）-（所属企微客服）刚在（店铺名）下单-（金额）
        msg_set = set()
        msg_list = []
        for customer_orm in customer_list:
            order_list = buyer_order_dict.get(customer_orm.taobao_id, [])

            order_info = [
                f"在({order.get('shop_name')})下单{order.get('price')}元，单号({order.get('trade_no')})"
                for order in order_list
            ]
            chat_info = await get_external_user_chat_info(customer_orm.weixin_id)
            follow_user_list = chat_info.get('follow_user', [])

            for follow_user in follow_user_list:
                follow_userid = follow_user.get('userid')
                weixin_user_orm: WeixinUser = await WeixinUser.filter(userid=follow_userid).first()
                owner_name = '未知'
                if weixin_user_orm: owner_name = weixin_user_orm.english_name or weixin_user_orm.username
                qiwei_customer_name = customer_orm.taobao_name or customer_orm.weixin_name

                content = f"({qiwei_customer_name}) -({owner_name})- 刚{', '.join(order_info)}"
                msg_hash_id = hashlib.md5(content.encode('utf-8')).hexdigest()
                if msg_hash_id in msg_set: continue
                msg_set.add(msg_hash_id)
                
                # 检查消息是否已存在
                msg_orm = await msg_controller.model.filter(hash_id=msg_hash_id).first()
                if msg_orm: continue

                msg = msg_controller.model(
                    hash_id=msg_hash_id,
                    title=qiwei_customer_name,
                    content=content,
                    detail=order_list,
                    type=MsgType.NEW_ORDER,
                    owner_id=follow_userid,
                    owner_name=owner_name
                )
                logger.info(f'创建新订单通知消息，{msg.content}')
                msg_list.append(msg)
        
        if msg_list:
            await msg_controller.model.bulk_create(msg_list)
        
        return msg_list, newest_order
    
    async def get_customer_by_weixin_id(self, weixin_id: str):
        async with cache_if(f'customer:user_datail_0:{weixin_id}', ttl=3600*24) as cache:
            if cache.hit:
                result = cache.value
                return result
            else:
                result = await self._get_customer_by_weixin_id(weixin_id)
                cache.set(result)
                return result
    
    async def _get_customer_by_weixin_id(self, weixin_id: str):
        chat_info = await get_external_user_chat_info(weixin_id)
        follow_user_list = chat_info.get('follow_user', [])
        external_contact = chat_info.get('external_contact', {})

        if external_contact:
            customer_orm = await self.model.filter(weixin_id=weixin_id).first()
            if customer_orm:
                customer_orm.weixin_avatar = external_contact.get('avatar')
                customer_orm.weixin_unionid = external_contact.get('unionid')
                customer_orm.weixin_name = external_contact.get('name')
                await customer_orm.save()
        
        user_ids = [follow_user.get('userid') for follow_user in follow_user_list]
        weixin_user_orm_list = await WeixinUser.filter(userid__in=user_ids).all()
        weixin_user_orm_dict = {weixin_user_orm.userid: weixin_user_orm for weixin_user_orm in weixin_user_orm_list}
        for follow_user in follow_user_list:
            follow_userid = follow_user.get('userid')
            weixin_user_orm = weixin_user_orm_dict.get(follow_userid)

            if weixin_user_orm:
                follow_user['username'] = weixin_user_orm.english_name or weixin_user_orm.username

        return {
            **external_contact,
            'follow_user_list': follow_user_list,
        }

        # for follow_user in follow_user_list:
        #     follow_userid = follow_user.get('userid')
    
    async def get_user_detail(self, weixin_id: str):
        async with cache_if(f'customer:user_datail:{weixin_id}', ttl=3600) as cache:
            if cache.hit:
                result = cache.value
                return result
            else:
                customer = await weixin_customer_controller.model.get_or_none(weixin_id=weixin_id)
                # if customer: return customer.to_dict()
                print(f'get_user_detail, weixin_id: {weixin_id}, customer: {customer}')

                # 通过中间表 CustomerGroup 查询其加入的所有群
                memberships = await CustomerGroup.filter(
                    customer=customer
                ).prefetch_related('group')  # 预加载 group 信息

                group_list = []
                result = {'group_list': group_list}
                for m in memberships:
                    data = await m.to_dict()
                    # data['group'] = await m.group.to_dict()
                    data['chat_name'] = m.group.name
                    data['create_time'] = m.group.create_time
                    group_list.append(data)

                chat_info = await self.get_customer_by_weixin_id(weixin_id)
                result['user_detail'] = chat_info
                cache.set(result)

                return result
    
    # 同步星云和数据库中的客户信息: 同步绑定信息到星云、从星云中加载订单信息
    async def sync_xingyun_contact_info(self, eager_load: bool = False):
        # 从星云同步绑定信息到本地
        await self._sync_xingyun_contact_info(eager_load=eager_load, order_id__isnull=False)
        # 从本地同步订单信息到星云
        await self._sync_xingyun_contact_info(eager_load=eager_load, order_id__isnull=True, limit=10)

    async def _sync_xingyun_contact_info(self, eager_load: bool = False, max_try: int = 2, limit: int = 20, **query_kwargs):
        query_kwargs = query_kwargs or {}
        
        last_id = 0
        while eager_load or max_try > 0:
            max_try -= 1
            if last_id:
                contact_orm_list = await self.model.filter(xingyun_sync=False, xingyun_id__isnull=False, id__lt=last_id, **query_kwargs).order_by('-id').limit(limit).all()
            else:
                contact_orm_list = await self.model.filter(xingyun_sync=False, xingyun_id__isnull=False, **query_kwargs).order_by('-id').limit(limit).all()

            if not contact_orm_list: break
            logger.debug(f'同步客户数据，{len(contact_orm_list)}')
            
            has_new = False
            update_list = []
            for contact_orm in contact_orm_list:
                last_id = contact_orm.id
                has_new = True

                if contact_orm.need_confirm: 
                    logger.info(f'客户需要确认绑定: {contact_orm}')
                    continue

                if contact_orm.order_id:
                    try: 
                        await self.bind_xingyun_user(contact_orm)
                        update_list.append(contact_orm)
                    except Exception as e:
                        logger.error(f'同步客户订单信息到星云错误，xingyun_id: {contact_orm.xingyun_id}, {e}')
                        continue
                else:
                    # list_user_order
                    try:
                        order_list = list_user_order(xy_client, cid=contact_orm.xingyun_id)
                    except Exception as e:
                        logger.error(f'客户数据同步错误，xingyun_id: {contact_orm.xingyun_id}, {e}')
                        logger.exception(e)
                        continue
                    
                    shopNameSet, order_id_list = set(), []
                    async for order in order_list:
                        shopName = order.get('shopName')
                        if shopName in shopNameSet:
                            continue
                        shopNameSet.add(shopName)
                        order_id_list.append(order.get('orderId'))
                        # 多店铺用的用的名称和ID都一样，所以不区分
                        break
                    
                    for order_id in order_id_list:
                        try:
                            await self.bind_order(WeixinOrderBindInfo(
                                userid=contact_orm.weixin_id,
                                order_id=order_id,
                            ))
                        except AssertionError as e:
                            logger.error(f'同步客户订单信息到本地错误，xingyun_id: {contact_orm.xingyun_id}, {e}')
                            logger.exception(e)
                            continue
                        contact_orm.xingyun_sync = True
                        update_list.append(contact_orm)
                        logger.info(f'同步客户订单信息到本地: {contact_orm.xingyun_id} -》 {order_id}')

            if update_list:
                await self.model.bulk_update(update_list, ['xingyun_sync'])
            
            if not has_new: break

weixin_customer_controller = WeixinCustomerController()
