"""
@author: luojunhui
"""
import traceback

from datetime import datetime, timedelta
from typing import List, Dict

from tqdm import tqdm
from urllib.parse import urlparse, parse_qs
from pymysql.cursors import DictCursor

from applications import bot
from applications import log
from applications import Functions
from applications import WeixinSpider
from applications.db import DatabaseConnector
from applications.const import UpdateMiniProgramDetailConst
from applications.exception import SpiderError
from config import long_articles_config, piaoquan_crawler_config

const = UpdateMiniProgramDetailConst()
spider = WeixinSpider()
functions = Functions()

TASK_NAME = "updateMinigramInfoDaily"
ARTICLE_TABLE = "official_articles_v2"
DETAIL_TABLE = "long_articles_detail_info"
EMPTY_LIST = []
EMPTY_DICT = {}


def extract_path(path: str) -> Dict:
    """
    提取path参数
    :param path:
    :return:
    """
    params = parse_qs(urlparse(path).query)
    jump_page = params.get('jumpPage', [None])[0]
    if jump_page:
        params2 = parse_qs(jump_page)
        res = {
            "video_id": params2['pages/user-videos?id'][0],
            "root_source_id": params2['rootSourceId'][0],
        }
        return res
    else:
        return EMPTY_DICT


def get_article_mini_program_info(content_url: str) -> List[Dict]:
    """
    获取文章的小程序信息
    :return:
    """
    try:
        article_detail = spider.get_article_text(content_url)
    except Exception as e:
        raise SpiderError(error=e, spider="detail", url=content_url)

    response_code = article_detail['code']
    if response_code == const.ARTICLE_SUCCESS_CODE:
        mini_info = article_detail['data']['data']['mini_program']
        return mini_info
    else:
        return EMPTY_LIST


class UpdatePublishedArticlesMinigramDetail(object):
    """
    更新已发布文章数据
    """

    def __init__(self):
        self.piaoquan_crawler_db_client = None
        self.long_articles_db_client = None

    def init_database(self) -> None:
        """
        init database connector
        :return:
        """
        # 初始化数据库连接
        try:
            self.piaoquan_crawler_db_client = DatabaseConnector(piaoquan_crawler_config)
            self.piaoquan_crawler_db_client.connect()
            self.long_articles_db_client = DatabaseConnector(long_articles_config)
            self.long_articles_db_client.connect()
        except Exception as e:
            error_msg = traceback.format_exc()
            bot(
                title="更新小程序裂变信息任务连接数据库失败",
                detail={
                    "error": e,
                    "msg": error_msg
                }
            )
            return

    def check_articles(self) -> List[Dict]:
        """
        校验是否存在文章未更新得到发布时间
        :return:
        """
        sql = f"""
            SELECT ContentUrl, wx_sn 
            FROM {ARTICLE_TABLE}
            WHERE publish_timestamp IN {(const.DEFAULT_STATUS, const.REQUEST_FAIL_STATUS)};
        """

        response = self.piaoquan_crawler_db_client.fetch(sql, cursor_type=DictCursor)
        return response

    def get_root_source_id_result(self, root_source_id: str, dt: str) -> Dict:
        """
        获取文章的root_source_id
        :param dt:
        :param root_source_id:
        :return:
        """
        select_sql = f"""
            SELECT first_uv, split0, split1, split2
            FROM changwen_data_rootsourceid
            WHERE root_source_id = '{root_source_id}' AND dt = '{dt}';
        """
        result = self.long_articles_db_client.fetch(select_sql, cursor_type=DictCursor)
        if result:
            return result[0]
        else:
            return EMPTY_DICT

    def get_articles_published_yesterday(self, biz_date: str) -> List[Dict]:
        """
        获取发布时间在biz_date前一天0点-23:59:59的文章
        :return:
        """
        sql = f"""
             SELECT ContentUrl, wx_sn, publish_timestamp, accountName, title
             FROM official_articles_v2
             WHERE FROM_UNIXTIME(publish_timestamp)
             BETWEEN DATE_SUB('{biz_date}', INTERVAL 1 DAY) AND DATE_SUB('{biz_date}', INTERVAL 1 SECOND);
         """
        article_list = self.piaoquan_crawler_db_client.fetch(query=sql, cursor_type=DictCursor)
        return article_list

    def insert_each_root_source_id(self, wx_sn, mini_title, mini_name, cover_url, video_index, root_source_id, video_id, publish_dt, recall_dt) -> int:
        """
        :param recall_dt: 召回日期
        :param publish_dt: 文章发布日期
        :param video_id: 视频id
        :param video_index: 视频位置
        :param cover_url: 视频封面
        :param mini_name: 小程序名称
        :param mini_title: 小程序标题
        :param wx_sn:
        :param root_source_id:
        :return:
        """
        insert_sql = f"""
            INSERT IGNORE INTO {DETAIL_TABLE}
            (wx_sn, mini_title, mini_name, cover_url, video_index, root_source_id, video_id, publish_dt, recall_dt)
            values
            (%s, %s, %s, %s, %s, %s, %s, %s, %s);
        """
        affected_rows = self.piaoquan_crawler_db_client.save(
            query=insert_sql,
            params=(
                wx_sn, mini_title, mini_name, cover_url, video_index, root_source_id, video_id, publish_dt, recall_dt
            )
        )
        return affected_rows

    def record_each_article(self, article_info: Dict) -> Dict:
        """
        记录每篇文章的root_source_id
        数量集: article_count * mini_program_count * days_count
        :param article_info:
        :return:
        """
        url = article_info['ContentUrl']
        publish_timestamp = article_info['publish_timestamp']
        wx_sn = article_info['wx_sn'].decode()

        article_mini_program_detail = get_article_mini_program_info(url)
        if article_mini_program_detail:
            log(
                task=TASK_NAME,
                function="record_each_article",
                message="获取文章链接对应的 rootSourceId 成功",
                data={
                    "ContentUrl": url,
                    "wxSn": wx_sn,
                    "publish_timestamp": publish_timestamp,
                    "miniInfo": article_mini_program_detail
                }
            )
            try:
                publish_date = datetime.fromtimestamp(publish_timestamp)
                # generate T+0, T+1, T+2 date string
                recall_dt_str_list = [
                    (publish_date + timedelta(days=i)).strftime('%Y-%m-%d')
                    for i in range(3)
                ]

                for date_str in recall_dt_str_list:
                    for video_index, mini_item in enumerate(article_mini_program_detail, 1):
                        image_url = mini_item['image_url']
                        nick_name = mini_item['nike_name']
                        # extract video id and root_source_id
                        id_info = extract_path(mini_item['path'])
                        root_source_id = id_info['root_source_id']
                        video_id = id_info['video_id']
                        kimi_title = mini_item['title']
                        self.insert_each_root_source_id(
                            wx_sn=wx_sn,
                            mini_title=kimi_title,
                            mini_name=nick_name,
                            cover_url=image_url,
                            video_index=video_index,
                            root_source_id=root_source_id,
                            video_id=video_id,
                            publish_dt=publish_date.strftime('%Y-%m-%d'),
                            recall_dt=date_str
                        )
                return EMPTY_DICT
            except Exception as e:
                error_msg = traceback.format_exc()
                log(
                    task=TASK_NAME,
                    function="record_each_article",
                    status="fail",
                    message="插入数据失败， 失败原因是{}--{}".format(e, error_msg)
                )
                return article_info

        else:
            return article_info

    def get_root_source_id_for_three_days(self, biz_date: str) -> List[Dict]:
        """
        获取publish_dt在 biz_date前三天的root_source_id
        :param biz_date:
        :return:
        """
        sql = f"""
             SELECT recall_dt, root_source_id
             FROM {DETAIL_TABLE}
             WHERE publish_dt
             BETWEEN DATE_SUB('{biz_date}', INTERVAL 3 DAY) AND DATE_SUB('{biz_date}', INTERVAL 1 SECOND);
        """
        article_list = self.piaoquan_crawler_db_client.fetch(query=sql, cursor_type=DictCursor)
        return article_list

    def update_each_root_source_id(self, recall_dt: str, root_source_id: str) -> None:
        """
        :param recall_dt:
        :param root_source_id:
        :return:
        """
        mini_program_detail = self.get_root_source_id_result(root_source_id=root_source_id, dt=recall_dt)
        if mini_program_detail:
            # do update job
            update_sql = f"""
                UPDATE {DETAIL_TABLE}
                SET first_level = %s, fission_0 = %s, fission_1 = %s, fission_2 = %s
                WHERE root_source_id = %s and recall_dt = %s;
            """
            self.piaoquan_crawler_db_client.save(
                query=update_sql,
                params=(
                    mini_program_detail['first_uv'],
                    mini_program_detail['split0'],
                    mini_program_detail['split1'],
                    mini_program_detail['split2'],
                    root_source_id,
                    recall_dt
                )
            )
        else:
            return

    def update_published_articles_job(self, biz_date=None):
        """
        将文章信息存入裂变表中
        :param biz_date:
        :return:
        """
        if not biz_date:
            biz_date = datetime.today().strftime('%Y-%m-%d')

        published_article_list = self.get_articles_published_yesterday(biz_date)
        failed_article_list = []
        for article_info in tqdm(published_article_list, desc="update_published_articles_job"):
            failed_article = self.record_each_article(article_info)
            if failed_article:
                failed_article_list.append(failed_article)

        # retry
        second_try_fail_article_list = []
        if failed_article_list:
            for failed_article in failed_article_list:
                second_fail = self.record_each_article(failed_article)
                if second_fail:
                    second_try_fail_article_list.append(second_fail)

        bot(
            title="更新文章任务完成",
            detail={
                "finish_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            },
            mention=False
        )
        if second_try_fail_article_list:
            bot(
                title="更新文章任务存在文章抓取失败",
                detail=[
                    {
                        "account": line['accountName'],
                        "title": line['title'],
                        "url": line['ContentUrl']
                    }
                    for line in second_try_fail_article_list
                ]
            )

    def update_mini_program_detail_job(self, biz_date=None):
        """
        update mini program detail info
        :param biz_date:
        :return:
        """
        if not biz_date:
            biz_date = datetime.today().strftime('%Y-%m-%d')

        # get root_source_id_list
        root_source_id_obj_list = self.get_root_source_id_for_three_days(biz_date)
        log(
            task=TASK_NAME,
            function="update_minigram_detail",
            message="获取前三天的 rootSourceId, 一共有 {} 条记录".format(len(root_source_id_obj_list))
        )
        fail_count = 0
        for item in tqdm(root_source_id_obj_list, desc="update_mini_program_detail_job"):
            try:
                self.update_each_root_source_id(
                    root_source_id=item['root_source_id'],
                    recall_dt=item['recall_dt']
                )
            except Exception as e:
                log(
                    task=TASK_NAME,
                    function="update_minigram_detail",
                    status="fail",
                    message="更新单条数据失败， 报错信息是 {}".format(e),
                    data={"error_msg": traceback.format_exc()}
                )
                fail_count += 1

        if fail_count:
            bot(
                title="{} fail because of lam db error".format(TASK_NAME),
                detail={
                    "fail_count": fail_count
                }
            )




