# -*- coding: utf-8 -*-
# by @Qist
"""
TVB云播
"""
import re
import json
import base64
import zlib
import random
import requests
from Crypto.Cipher import AES
from base.spider import Spider  # 继承基础Spider类


# ------------------------------------------------------------------ #
# 响应解密（AES-128-CBC + gzip），key/iv 来自 libnmmp.so
# ------------------------------------------------------------------ #
_AES_KEY = b"MickwrKuMickwrKu"
_AES_IV = b"MickwrKuMickwrKu"


def _decrypt_data(b64text):
    """data 字段(base64) -> 明文 JSON 字符串"""
    if not b64text:
        return None
    try:
        raw = base64.b64decode(b64text)
        pt = AES.new(_AES_KEY, AES.MODE_CBC, _AES_IV).decrypt(raw)
        pad = pt[-1]
        if 1 <= pad <= 16:
            pt = pt[:-pad]
        try:
            return zlib.decompress(pt, 16 + zlib.MAX_WBITS).decode('utf-8', 'ignore')
        except Exception:
            return pt.decode('utf-8', 'ignore')
    except Exception as e:
        print(f"[hktv] decrypt fail: {e}")
        return None


# 线路名 -> 中文显示名（源站返回的是英文/拼音缩写）
_LINE_CN = {
    'mp4': 'MP4 高清',
    'YYNB': '优优牛播',
    'wsym3u8': '微视云 M3U8',
    'bfzym3u8': '暴风影视 M3U8',
    'lzm3u8': '量子 M3U8',
    'hkm3u8': '港剧 M3U8',
    '1080zyk': '1080 自愈库',
}


def _line_cn(name):
    """线路名转中文显示；未知线路保留原名。"""
    return _LINE_CN.get(name, name)


# 随机机型池：观看端一般是电视盒 / 手机，混合使用以模拟真实 UA。
_DEVICE_MODELS = [
    # 电视盒
    'ZXV10S100V7', 'MIBOX4', 'MIBOX3', 'ADT-2', 'ADT-3',
    'HWVTR-H', 'EC6108V9', 'Q21A', 'B860H', 'S905X3',
    'FiberHome-HG680', 'ZTE-B860AV2.1', 'Skyworth-E900V21E',
    # 手机
    'SM-S918B', 'SM-G998B', 'SM-S901B', 'Pixel-7', 'Pixel-8-Pro',
    '2201123G', 'M2012K11AG', 'PHK110', 'PAHM00', 'RMX3370',
]


def _rand_device_model():
    """随机选一个机型，模拟电视盒 / 手机观看端。"""
    return random.choice(_DEVICE_MODELS)


class Spider(Spider):
    def getName(self):
        return 'HKTV影视'

    def init(self, extend=""):
        if extend and extend.strip():
            self.host = extend.strip().rstrip('/')
        return self.host

    def __init__(self):
        self.name = 'HKTV影视'
        self.host = 'http://app.tuxianimg.com'
        self.timeout = 25
        # 随机机型（电视盒 / 手机混合），每台实例固定一次，模拟真实观看端 UA。
        self.device_model = _rand_device_model()
        # 注：Host / Content-Length 由 requests 按 URL 与 body 自动生成，不在此手动设置。
        self.header = {
            'App-Device-Id': '2c84565e46955353d875e3d964d87e1c6',
            'App-Os-Type': 'android',
            'App-Ui-Mode': 'light',
            'App-Version-Code': '100',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Connection': 'Keep-Alive',
            'Accept-Encoding': 'gzip',
            'User-Agent': 'okhttp/3.14.9',
        }
        # 由 playerContent 作为“全局播放 header”返回，框架会透传到所有直链请求。
        # 注：host 字段由 requests/框架按 URL 自动生成，不在此手动设置（否则跨域名会错）。
        self.play_header = {
            'User-Agent': f'Dalvik/2.1.0 (Linux; U; Android 15; {self.device_model} Build/b5cc949.1)',
            'accept-encoding': 'gzip',
            'allowcrossprotocolredirects': 'true',
            'connection': 'Keep-Alive',
        }
        # 运行时由 init 接口填充
        self.class_names = []
        self.class_urls = []
        self.type_extend = {}   # type_id -> {class/area/lang/year/sort 选项}

    # ------------------------------------------------------------------ #
    # 内部：统一 API 请求 + 解密
    # ------------------------------------------------------------------ #
    def _api(self, endpoint, params=None):
        url = f"{self.host}/api/vod/{endpoint}"
        try:
            resp = requests.post(url, data=params or {}, headers=self.header,
                                 timeout=self.timeout, verify=False)
            resp.encoding = 'utf-8'
            if resp.status_code != 200:
                print(f"[hktv] {endpoint} HTTP {resp.status_code}")
                return None
            try:
                obj = resp.json()
            except Exception:
                m = re.search(r'\{.*\}', resp.text, re.S)
                obj = json.loads(m.group(0)) if m else None
            if not obj:
                return None
            plain = _decrypt_data(obj.get('data'))
            if not plain:
                return None
            return json.loads(plain)
        except Exception as e:
            print(f"[hktv] request {endpoint} failed: {e}")
            return None

    @staticmethod
    def _pic(pic):
        if not pic:
            return ''
        return pic if pic.startswith('http') else pic

    @staticmethod
    def _vod_item(v):
        pic = v.get('vod_pic') or v.get('vod_pic_slide') or ''
        return {
            'vod_id': str(v.get('vod_id', '') or ''),
            'vod_name': v.get('vod_name', '') or '',
            'vod_pic': Spider._pic(pic),
            'vod_remarks': v.get('vod_remarks', '') or '',
        }

    # ------------------------------------------------------------------ #
    # 框架接口（与 tvb.py 完全一致）
    # ------------------------------------------------------------------ #
    def homeContent(self, filter):
        try:
            result = {'class': [], 'list': []}
            init = self._api('init')
            if init:
                # 分类
                for c in init.get('type_list', []):
                    tid = str(c.get('type_id', ''))
                    tname = c.get('type_name', '')
                    if not tid or not tname:
                        continue
                    if tid == '0' and tname == '全部':
                        continue  # 跳过“全部”虚拟项
                    self.class_names.append(tname)
                    self.class_urls.append(tid)
                    # 解析筛选维度
                    ext = c.get('type_extend', '')
                    if ext:
                        try:
                            self.type_extend[tid] = json.loads(ext)
                        except Exception:
                            pass
                # 首页推荐
                for v in init.get('recommend_list', []):
                    item = self._vod_item(v)
                    if item['vod_id'] and item['vod_name']:
                        result['list'].append(item)

            for name, cid in zip(self.class_names, self.class_urls):
                result['class'].append({'type_name': name, 'type_id': cid})

            if filter:
                result['filters'] = {}
                for cid in self.class_urls:
                    result['filters'][cid] = self.get_filter_data(cid)

            return result
        except Exception as e:
            print(f"Error in homeContent: {e}")
            import traceback
            traceback.print_exc()
            return {'class': [], 'list': []}

    def get_filter_data(self, tid):
        """
        筛选维度来自 init 的 type_extend（真实数据）；无则用预定义兜底。
        维度：class(类型)/area(地区)/lang(语言)/year(年份)/sort(排序)
        """
        try:
            ext = self.type_extend.get(str(tid))
            if ext:
                out = []
                label_map = {'class': '类型', 'area': '地区', 'lang': '语言',
                             'year': '年份', 'sort': '排序', 'star': '明星',
                             'director': '导演', 'state': '状态', 'version': '版本'}
                for key, label in label_map.items():
                    vals = ext.get(key)
                    if not vals:
                        continue
                    items = [{'n': '全部', 'v': ''}]
                    for v in vals.split(','):
                        v = v.strip()
                        if v:
                            items.append({'n': v, 'v': v})
                    out.append({'key': key, 'name': label, 'value': items})
                if out:
                    return out
            # 兜底
            return [
                {'key': 'class', 'name': '类型', 'value': [{'n': '全部', 'v': ''}, {'n': '动作', 'v': '动作'}, {'n': '喜剧', 'v': '喜剧'}, {'n': '爱情', 'v': '爱情'}, {'n': '科幻', 'v': '科幻'}, {'n': '剧情', 'v': '剧情'}, {'n': '犯罪', 'v': '犯罪'}, {'n': '奇幻', 'v': '奇幻'}]},
                {'key': 'area', 'name': '地区', 'value': [{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'}, {'n': '台湾', 'v': '台湾'}, {'n': '美国', 'v': '美国'}, {'n': '日本', 'v': '日本'}, {'n': '韩国', 'v': '韩国'}]},
                {'key': 'lang', 'name': '语言', 'value': [{'n': '全部', 'v': ''}, {'n': '国语', 'v': '国语'}, {'n': '英语', 'v': '英语'}, {'n': '粤语', 'v': '粤语'}]},
                {'key': 'year', 'name': '年份', 'value': [{'n': '全部', 'v': ''}, {'n': '2026', 'v': '2026'}, {'n': '2025', 'v': '2025'}, {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}]},
                {'key': 'sort', 'name': '排序', 'value': [{'n': '全部', 'v': ''}, {'n': '时间', 'v': 'time'}, {'n': '人气', 'v': 'hits'}, {'n': '评分', 'v': 'score'}]},
            ]
        except Exception as e:
            print(f"Error in get_filter_data: {e}")
            return []

    def categoryContent(self, tid, pg, filter, extend):
        try:
            params = {'type_id': tid, 'page': pg}
            if extend:
                for key in ('class', 'area', 'lang', 'year', 'sort'):
                    val = extend.get(key)
                    if val:
                        params[key] = val

            data = self._api('typeFilterVodList', params)
            if not data:
                return {'list': [], 'page': int(pg), 'pagecount': 0,
                        'limit': 0, 'total': 0}

            rows = data.get('recommend_list') or []
            videos = [self._vod_item(v) for v in rows
                      if v.get('vod_id') and v.get('vod_name')]

            seen = set()
            uniq = []
            for v in videos:
                if v['vod_id'] not in seen:
                    seen.add(v['vod_id'])
                    uniq.append(v)

            total = data.get('total', len(uniq))
            pagecount = data.get('page_count', data.get('pagecount', 999))
            return {
                'list': uniq,
                'page': int(pg),
                'pagecount': pagecount,
                'limit': len(uniq),
                'total': total,
            }
        except Exception as e:
            print(f"Error in categoryContent: {e}")
            import traceback
            traceback.print_exc()
            return {'list': [], 'page': int(pg), 'pagecount': 0,
                    'limit': 0, 'total': 0}

    def detailContent(self, ids):
        try:
            if not ids or not ids[0]:
                return {'list': []}
            vid = ids[0]
            data = self._api('vodDetail', {'vod_id': vid})
            if not data:
                return {'list': []}

            v = data.get('vod') or {}
            if not isinstance(v, dict):
                return {'list': []}

            vod = {
                'vod_id': str(v.get('vod_id', vid)),
                'vod_name': v.get('vod_name', '') or '',
                'vod_pic': self._pic(v.get('vod_pic') or v.get('vod_pic_slide') or ''),
                'vod_remarks': v.get('vod_remarks', '') or '',
                'vod_year': v.get('vod_year', '') or '',
                'vod_area': v.get('vod_area', '') or '',
                'vod_actor': v.get('vod_actor', '') or '',
                'vod_director': v.get('vod_director', '') or '',
                'vod_content': v.get('vod_content', '') or '',
            }

            # 线路：from_list 名 + url_list 集（url 在 vodParse 中解密）
            from_list = data.get('vod_play_from_list') or []
            url_list = data.get('vod_play_url_list') or []
            src_list = data.get('player_source_list') or []
            # player_code -> player_source_id
            code2id = {s.get('player_code'): s.get('id') for s in src_list if s.get('player_code')}

            play_from = []
            play_url = []
            for idx, src in enumerate(from_list):
                psid = code2id.get(src, '')
                episodes = []
                if idx < len(url_list):
                    for ep in url_list[idx].get('urls', []):
                        # 编码: 名称$player_source_id@episode_index@vod_id
                        episodes.append(f"{ep.get('name','')}${psid}@{ep.get('episode_index','')}@{vid}")
                play_from.append(_line_cn(src))
                play_url.append('#'.join(episodes))

            vod['vod_play_from'] = '$$$'.join(play_from)
            vod['vod_play_url'] = '$$$'.join(play_url)
            # 保存解析所需的 id 映射，供 playerContent 使用
            vod['_player_source'] = {src: code2id.get(src) for src in play_from}
            vod['_vod_id'] = vid
            return {'list': [vod]}
        except Exception as e:
            print(f"Error in detailContent: {e}")
            import traceback
            traceback.print_exc()
            return {'list': []}

    def searchContent(self, key, quick, pg="1"):
        try:
            # 真实搜索接口：searchList，参数 keywords / type_id / page
            data = self._api('searchList', {'keywords': key, 'type_id': '0', 'page': pg})
            if not data:
                return {'list': []}
            rows = data.get('search_list') or data.get('recommend_list') or []
            videos = []
            seen = set()
            for v in rows:
                item = self._vod_item(v)
                if item['vod_id'] and item['vod_name'] and item['vod_id'] not in seen:
                    seen.add(item['vod_id'])
                    videos.append(item)
            return {'list': videos}
        except Exception as e:
            print(f"Error in searchContent: {e}")
            import traceback
            traceback.print_exc()
            return {'list': []}

    def searchSuggestions(self, keyword):
        """
        搜索联想（实时补全）。参数 keyword。
        与 searchContent 共用同一套解密；返回建议名称列表。
        """
        try:
            data = self._api('searchSuggestions', {'keyword': keyword})
            if not data:
                return []
            out = []
            for it in data.get('list', []) or []:
                if it.get('vod_name'):
                    out.append(it['vod_name'])
            return out
        except Exception as e:
            print(f"Error in searchSuggestions: {e}")
            return []

    def playerContent(self, flag, id, vipFlags):
        """
        detailContent 中每集被编码为 "name$psid@epidx@vodid"，
        框架传入的 id 形态为 "psid@epidx@vodid"（不含名称）。
        经 vodParse 接口用 (vod_id, player_source_id, episode_index) 换取真实 play_url。
        """
        try:
            if not id or id.count('@') < 2:
                return {'parse': 0, 'url': '', 'header': {}, 'playUrl': ''}
            psid, ep_idx, vid = id.split('@', 2)
            data = self._api('vodParse', {
                'vod_id': vid,
                'player_source_id': psid,
                'episode_index': ep_idx,
            })
            if data and data.get('play_url'):
                return {
                    'parse': 1 if data['play_url'].startswith('http') else 0,
                    'url': data['play_url'],
                    'header': self.play_header,
                    'playUrl': '',
                }
            return {'parse': 0, 'url': '', 'header': {}, 'playUrl': ''}
        except Exception as e:
            print(f"Error in playerContent: {e}")
            return {'parse': 0, 'url': '', 'header': {}, 'playUrl': ''}

    def fetch(self, url):
        try:
            response = requests.get(url, headers=self.header, timeout=self.timeout)
            response.encoding = 'utf-8'
            return response.text if response.status_code == 200 else None
        except Exception as e:
            print(f"Error fetching {url}: {e}")
            return None

    def get_id_from_href(self, href):
        if not href:
            return href
        m = re.search(r'[?&]id=(\d+)', href)
        if m:
            return m.group(1)
        m = re.search(r'/id/(\d+)', href)
        if m:
            return m.group(1)
        return href
