"""
$description US live TV channels. OTT service from USTVnow.
$url ustvnow.com
$type live
$account Required, additional subscription required by some streams
"""

import base64
import json
import re
from urllib.parse import urljoin, urlparse
from uuid import uuid4

from streamlink.logger import getLogger
from streamlink.plugin import Plugin, PluginError, pluginargument, pluginmatcher
from streamlink.stream.hls import HLSStream
from streamlink.utils.crypto import AES, SHA256, pad, unpad


log = getLogger(__name__)


@pluginmatcher(
    re.compile(r"https?://(?:www\.)?ustvnow\.com/channel/live/(?P<chname>\w+)"),
)
@pluginargument(
    "username",
    required=True,
    requires=["password"],
    metavar="USERNAME",
    help="Your USTV Now account username",
)
@pluginargument(
    "password",
    required=True,
    sensitive=True,
    metavar="PASSWORD",
    help="Your USTV Now account password",
)
class USTVNow(Plugin):
    _main_js_re = re.compile(r"""src=['"](main\..*\.js)['"]""")
    _enc_key_re = re.compile(r'(?P<key>AES_(?:Key|IV))\s*:\s*"(?P<value>[^"]+)"')

    TENANT_CODE = "ustvnow"
    _api_url = "https://teleupapi.revlet.net/service/api/v1/"
    _token_url = _api_url + "get/token"
    _signin_url = "https://www.ustvnow.com/signin"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._encryption_config = {}
        self._token = None

    @classmethod
    def encrypt_data(cls, data, key, iv):
        rkey = "".join(reversed(key)).encode("utf8")
        riv = "".join(reversed(iv)).encode("utf8")

        fkey = SHA256.new(rkey).hexdigest()[:32].encode("utf8")

        cipher = AES.new(fkey, AES.MODE_CBC, riv)
        encrypted = cipher.encrypt(pad(data, 16, "pkcs7"))
        return base64.b64encode(encrypted)

    @classmethod
    def decrypt_data(cls, data, key, iv):
        rkey = "".join(reversed(key)).encode("utf8")
        riv = "".join(reversed(iv)).encode("utf8")

        fkey = SHA256.new(rkey).hexdigest()[:32].encode("utf8")

        cipher = AES.new(fkey, AES.MODE_CBC, riv)
        decrypted = cipher.decrypt(base64.b64decode(data))
        if decrypted:
            return unpad(decrypted, 16, "pkcs7")
        else:
            return decrypted

    def _get_encryption_config(self, url):
        # find the path to the main.js
        # load the main.js and extract the config
        if not self._encryption_config:
            res = self.session.http.get(url)
            m = self._main_js_re.search(res.text)
            main_js_path = m and m.group(1)
            if main_js_path:
                res = self.session.http.get(urljoin(url, main_js_path))
                self._encryption_config = dict(self._enc_key_re.findall(res.text))

        return self._encryption_config.get("AES_Key"), self._encryption_config.get("AES_IV")

    @property
    def box_id(self):
        if not self.cache.get("box_id"):
            self.cache.set("box_id", str(uuid4()))
        return self.cache.get("box_id")

    def get_token(self):
        if not self._token:
            log.debug("Getting new session token")
            res = self.session.http.get(
                self._token_url,
                params={
                    "tenant_code": self.TENANT_CODE,
                    "box_id": self.box_id,
                    "product": self.TENANT_CODE,
                    "device_id": 5,
                    "display_lang_code": "ENG",
                    "device_sub_type": "",
                    "timezone": "UTC",
                },
            )

            data = res.json()
            if not data["status"]:
                log.error("Token acquisition failed: {details} ({detail})".format(**data["error"]))
                raise PluginError("could not obtain token")

            self._token = data["response"]["sessionId"]
            log.debug(f"New token: {self._token}")

        return self._token

    def api_request(self, path, data, metadata=None):
        key, iv = self._get_encryption_config(self._signin_url)
        post_data = {
            "data": self.encrypt_data(json.dumps(data).encode("utf8"), key, iv).decode("utf8"),
            "metadata": self.encrypt_data(json.dumps(metadata).encode("utf8"), key, iv).decode("utf8"),
        }
        headers = {
            "box-id": self.box_id,
            "session-id": self.get_token(),
            "tenant-code": self.TENANT_CODE,
            "content-type": "application/json",
        }
        res = self.session.http.post(self._api_url + path, data=json.dumps(post_data), headers=headers).json()
        data = {k: v and json.loads(self.decrypt_data(v, key, iv)) for k, v in res.items()}
        return data

    def login(self, username, password):
        log.debug("Trying to login...")
        resp = self.api_request(
            "send",
            {
                "login_id": username,
                "login_key": password,
                "login_mode": "1",
                "manufacturer": "123",
            },
            {"request": "signin"},
        )

        return resp["data"]["status"]

    def _get_streams(self):
        if not self.login(self.get_option("username"), self.get_option("password")):
            log.error("Failed to login, check username and password")
            return

        path = urlparse(self.url).path.strip("/")
        resp = self.api_request("send", {"path": path}, {"request": "page/stream"})
        if not resp["data"]["status"]:
            log.error("Could not find any streams: {code}: {message}".format(**resp["data"]["error"]))
            return

        for stream in resp["data"]["response"]["streams"]:
            if stream["keys"]["licenseKey"]:
                log.warning("Stream possibly protected by DRM")
            yield from HLSStream.parse_variant_playlist(self.session, stream["url"]).items()


__plugin__ = USTVNow
