import httpx
import json
import traceback
from hashlib import md5
from loguru import logger

from .captcha import recognize


class Tree:  # 任务树
    def __init__(self, task):
        self.task = task
        self.child = []

    def sort(self):
        try:
            self.child.sort(
                key=lambda node: node.task['displayOrder']
            )
            for ch in self.child:
                ch.sort()
        except Exception:
            logger.warning(f'[构建任务树] | 排序出错：\n{traceback.format_exc()}')


class Spider(httpx.AsyncClient):
    def __init__(self):
        super().__init__(follow_redirects=True)
        self.is_login = False

    async def login(self, username, password):
        try:
            if self.is_login:
                return {}

            self.cookies.clear()  # 重置 cookies
            logger.info('[登录] | 正在获取验证码...')
            result = await self.get('https://sso.ismartlearning.cn/captcha.html')
            code = recognize(result.content)
            password = md5(md5(password.encode()).hexdigest().encode() + b'fa&s*l%$k!fq$k!ld@fjlk').hexdigest()
            logger.info('[登录] | 正在登录...')
            info = (await self.post(
                'https://sso.ismartlearning.cn/v2/tickets-v2',
                data={
                    'username': username,
                    'password': password,
                    'captcha': code
                },
                headers={
                    'X-Requested-With': 'XMLHttpRequest',
                    'Origin': 'https://me.ismartlearning.cn',
                    'Referer': 'https://me.ismartlearning.cn/'
                }
            )).json()['result']
            logger.debug(f"[登录] | {info}")
            assert info['code'] == -26  # 断言登录结果
            self.is_login = True
            logger.success('[登录] | 登录成功')
            return info
        except Exception:
            logger.warning(f'[登录] | 登录出错：\n{traceback.format_exc()}')

    async def get_courses(self):  # 获取课程列表
        try:
            logger.info('[获取课程列表] | 正在获取课程列表...')
            courses = (await self.post(
                'https://school.ismartlearning.cn/client/course/list-of-student?status=1',
                data={
                    'pager.currentPage': 1,
                    'pager.pageSize': 100
                }
            )).json()['data']['list']
            logger.debug(f"[获取课程列表] | {courses}")
            logger.success('[获取课程列表] | 获取课程列表成功')
            return courses
        except Exception:
            logger.warning(f'[获取课程列表] | 获取课程列表出错：\n{traceback.format_exc()}')

    async def get_books(self, course_id):  # 获取某课程的书籍列表
        try:
            await self.post(  # 必须有这个请求，否则后面会报错
                'https://school.ismartlearning.cn/client/course/list-of-student?status=1',
                data={
                    'pager.currentPage': 1,
                    'pager.pageSize': 100
                }
            )
            books = (await self.post(
                'https://school.ismartlearning.cn/client/course/textbook/list-of-student',
                data={
                    'courseId': course_id
                }
            )).json()['data']
            return books
        except Exception:
            logger.warning(f'[获取书籍列表] | 获取书籍列表出错：\n{traceback.format_exc()}')

    async def get_tasks(self, book_id, book_type, course_id):  # 获取某书籍的任务树
        try:
            logger.info('[获取任务列表] | 正在获取任务列表...')
            await self.post('https://school.ismartlearning.cn/client/course/textbook/chapters')
            tasks = (await self.post(
                'https://school.ismartlearning.cn/client/course/textbook/chapters',
                data={
                    'bookId': book_id,
                    'bookType': book_type,
                    'courseId': course_id
                }
            )).json()['data']
            id_record = {task['id']: Tree(task) for task in tasks}
            book_name = (await self.book_info(book_id))['bookName']
            root = Tree({
                'book_id': tasks[0]['book_id'],
                'unitStudyPercent': 0,
                'name': book_name
            })
            logger.info('[构建任务树] | 正在构建任务树...')
            for task_id in id_record:
                node = id_record[task_id]
                node_name = f'{node.task.get("name","")}[id:{node.task["id"]}]'
                if 'parent_id' in node.task:
                    if node.task['parent_id'] in id_record:
                        id_record[node.task['parent_id']].child.append(node)
                    else:
                        logger.warning(f'[构建任务树] | {node_name} 父节点不存在')
                else:
                    root.child.append(node)
            root.sort()
            logger.success('[构建任务树] | 构建任务树完成')
            logger.success('[获取任务列表] | 获取任务列表完成')
            return root
        except Exception:
            logger.warning(f'[获取任务列表] | 获取任务列表出错：\n{traceback.format_exc()}')

    async def get_paper(self, paper_id):  # 获取任务点信息（包括题目和答案）
        try:
            logger.info('[获取任务点] | 正在获取任务点信息...')
            ticket = (await self.post(
                'https://sso.ismartlearning.cn/v1/serviceTicket',
                data={
                    'service': 'https://xot-api.ismartlearning.cn/client/textbook/paperinfo'
                }
            )).json()['data']['serverTicket']
            logger.debug(f'[获取任务点] | {ticket}')
            paper_info = (await self.post(
                'https://xot-api.ismartlearning.cn/client/textbook/paperinfo',
                params={
                    'ticket': ticket
                },
                data={
                    'paperId': paper_id
                },
                headers={
                    'Origin': 'https://me.ismartlearning.cn',
                    'Referer': 'https://me.ismartlearning.cn/',
                    'X-Requested-With': 'XMLHttpRequest',
                    'Accept-Encoding': 'gzip, deflate'
                }
            )).json()['data']
            # logger.debug(f'[获取任务点] | {json.dumps(paper_info, indent=4)}')
            logger.success('[获取任务点] | 获取任务点信息完成')
            return paper_info
        except Exception:
            logger.warning(f'[获取任务点] | 获取任务点出错：\n{traceback.format_exc()}')

    async def user_info(self):
        try:
            logger.info('[获取用户信息] | 正在获取用户信息...')
            info = (await self.post('https://school.ismartlearning.cn/client/user/student-info')).json()
            logger.success('[获取用户信息] | 获取用户信息完成')
            return info
        except Exception:
            logger.warning(f'[获取用户信息] | 获取用户信息出错：\n{traceback.format_exc()}')

    async def book_info(self, book_id):
        try:
            logger.info('[获取书籍信息] | 正在获取书籍信息...')
            ticket = (await self.post(
                'https://sso.ismartlearning.cn/v1/serviceTicket',
                data={
                    'service': 'https://book-api.ismartlearning.cn/client/v2/book/info'
                }
            )).json()['data']['serverTicket']
            logger.debug(f'[获取书籍信息] | {ticket}')
            book_info = (await self.post(
                'https://book-api.ismartlearning.cn/client/v2/book/info',
                params={
                    'ticket': ticket
                },
                data={
                    'bookId': book_id,
                    'bookType': 0
                },
                headers={
                    'Origin': 'https://me.ismartlearning.cn',
                    'Referer': 'https://me.ismartlearning.cn/',
                    'X-Requested-With': 'XMLHttpRequest',
                    'Accept-Encoding': 'gzip, deflate'
                }
            )).json()['data']
            logger.debug(f'[获取书籍信息] |  {json.dumps(book_info, indent=4)}')
            logger.success('[获取书籍信息] | 获取书籍信息完成')
            return book_info
        except Exception:
            logger.warning(f'[获取书籍信息] | 获取书籍信息出错：\n{traceback.format_exc()}')
