#!/usr/bin/python3
import sys
import os
import base64
import json
import datetime
import pytz
import hmac
import hashlib
import time
import uuid
import random
import string
from urllib.parse import urlparse

# Add parent directory to path for o11 import
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import o11

from bs4 import BeautifulSoup
from pyplayready.cdm import Cdm
from pyplayready.device import Device
from pyplayready.system.pssh import PSSH

# Parse command line parameters
user = o11.parse_params(sys.argv, 'user')
password = o11.parse_params(sys.argv, 'password')
device = o11.parse_params(sys.argv, 'device')
pin = o11.parse_params(sys.argv, 'pin')

id = o11.parse_params(sys.argv, 'id')
action = o11.parse_params(sys.argv, 'action')

bind = o11.parse_params(sys.argv, 'bind')
proxy = o11.parse_params(sys.argv, 'proxy')
doh = o11.parse_params(sys.argv, 'doh')
worker = o11.parse_params(sys.argv, 'worker')

cdm = o11.parse_params(sys.argv, 'cdm')
drm = o11.parse_params(sys.argv, 'drm')
kid = o11.parse_params(sys.argv, 'kid')
pssh = o11.parse_params(sys.argv, 'pssh')
challenge = o11.parse_params(sys.argv, 'challenge')

heartbeaturl = o11.parse_params(sys.argv, 'heartbeaturl')
heartbeatparams = o11.parse_params(sys.argv, 'heartbeatparams')

# Session setup
o11Session = o11.session(bind=bind, proxy=proxy, worker=worker)
req = o11Session.get_session()
if doh != "":
    o11.dns(doh)

# Configuration
WVD_PATH = './WVD.wvd'
authFile = '/NowTV_PR_auth.json'
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))

USER_AGENT = 'NowottAndroid-GB/6.4.13-124060413'
PLATFORM = 'ANDROIDTV'
DEVICE = 'TV'
VIDEO_FORMAT = 'UHD'
MODEL = 'SHARP-AQUOS-DMP'

token = ''
territory = 'GB'

def get_auth():
    try:
        return json.load(open(SCRIPT_DIR + authFile))
    except:
        return None

def save_auth(auth_data):
    json.dump(auth_data, open(SCRIPT_DIR + authFile, 'w'), indent=2)

def generate_device_id(length=44):
    characters = string.ascii_uppercase + string.ascii_lowercase + string.digits
    return ''.join(random.choices(characters, k=length))

def calculate_signature(method, url, headers, payload, timestamp=None):
    app_id = 'NOWOTT-ANDROID-v1'
    signature_key = bytearray('He97trFdwMSKZBbnJGjzyPXN3Qgu2qRvh4spkmcC', 'utf-8')
    sig_version = '1.0'
    if not timestamp:
        timestamp = int(time.time())
    if url.startswith('http'):
        parsed_url = urlparse(url)
        path = parsed_url.path
    else:
        path = url
    text_headers = ''
    for key in sorted(headers.keys()):
        if key.lower().startswith('x-skyott'):
            text_headers += key.lower() + ': ' + headers[key] + '\n'
    headers_md5 = hashlib.md5(text_headers.encode()).hexdigest()
    if isinstance(payload, str):
        payload = payload.encode('utf-8')
    payload_md5 = hashlib.md5(payload).hexdigest()
    to_hash = f'{method}\n{path}\n\n{app_id}\n{sig_version}\n{headers_md5}\n{timestamp}\n{payload_md5}\n'
    hashed = hmac.new(signature_key, to_hash.encode('utf8'), hashlib.sha1).digest()
    signature = base64.b64encode(hashed).decode('utf8')
    return f'SkyOTT client="{app_id}",signature="{signature}",timestamp="{timestamp}",version="{sig_version}"'

def get_territory():
    response = req.get('https://geolocation.onetrust.com/cookieconsentpub/v1/geo/location', headers={'accept': 'application/json', 'origin': 'https://www.nowtv.com', 'referer': 'https://www.nowtv.com/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'})
    return response.json()['country']

def get_persona(skyCEsidismesso01):
    headers = {'accept': 'application/vnd.persona.v1+json', 'content-type': 'application/vnd.persona.v1+json', 'origin': 'https://www.nowtv.com', 'referer': 'https://www.nowtv.com/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'x-skyid-token': skyCEsidismesso01, 'x-skyott-device': 'COMPUTER', 'x-skyott-platform': 'PC', 'x-skyott-proposition': 'NOWTV', 'x-skyott-provider': 'NOWTV', 'x-skyott-territory': territory, 'x-skyott-tokentype': 'SSO'}
    response = req.get('https://persona-store.sky.com/persona-store/personas', headers=headers)
    return response.json()['personas'][0]['personaId']

def token_request(skyCEsidismesso01, device_id, persona_id):
    url = 'https://ovp.nowtv.com/auth/throttled/tokens'
    headers = {'Accept': 'application/vnd.tokens.v1+json', 'User-Agent': USER_AGENT, 'X-SkyOTT-Provider': 'NOWTV', 'X-SkyOTT-Proposition': 'NOWOTT', 'X-SkyOTT-Platform': PLATFORM, 'X-SkyOTT-Device': DEVICE, 'X-SkyOTT-Language': 'en-GB', 'X-SkyOTT-Territory': territory, 'X-SkyOTT-ActiveTerritory': territory, 'Content-Type': 'application/vnd.tokens.v1+json'}
    data = {'auth': {'authScheme': 'MESSO', 'authToken': skyCEsidismesso01, 'provider': 'NOWTV', 'providerTerritory': territory, 'proposition': 'NOWOTT', 'personaId': persona_id}, 'device': {'type': DEVICE, 'platform': PLATFORM, 'id': device_id, 'drmDeviceId': 'UNKNOWN'}}
    data = json.dumps(data)
    headers['x-sky-signature'] = calculate_signature('POST', url, headers, data)
    response = req.post(url, headers=headers, data=data)
    return response.json()['userToken']

def login():
    global token, territory
    print("logging in...", file=sys.stderr)
    
    auth = get_auth()
    if not auth or 'skyCEsidismesso01' not in auth:
        print("No auth found. Please add NowTV_PR skyCEsidismesso01 cookie to auth file.", file=sys.stderr)
        save_auth({'skyCEsidismesso01': '', 'device_id': generate_device_id()})
        sys.exit(1)
    
    try:
        territory = get_territory()
        persona_id = get_persona(auth['skyCEsidismesso01'])
        token = token_request(auth['skyCEsidismesso01'], auth['device_id'], persona_id)
        print("logged in successfully", file=sys.stderr)
        return token
    except Exception as e:
        print(f"Login failed: {e}", file=sys.stderr)
        sys.exit(1)

def get_token():
    global token
    if token:
        return token
    return login()

def get_pssh_from_mpd(url):
    response = req.get(url, headers={'user-agent': USER_AGENT})
    content_protections = BeautifulSoup(response.content, features="xml").findAll('ContentProtection')
    for cp in content_protections:
        if cp.get('schemeIdUri', '').lower() == 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95':
            pssh_elem = cp.find('cenc:pssh')
            if pssh_elem:
                return pssh_elem.text
    return None

def get_single(content_id, playouts_type='live'):
    url = f'https://ovp.nowtv.com/video/playouts/{playouts_type}'
    headers = {'User-Agent': USER_AGENT, 'x-skyott-usertoken': token, 'x-skyott-territory': territory, 'x-skyott-provider': 'NOWTV', 'x-skyint-requestid': str(uuid.uuid4()), 'x-skyott-proposition': 'NOWOTT', 'x-skyott-device': DEVICE, 'x-skyott-platform': PLATFORM, 'x-skyott-activeterritory': territory, 'x-skyott-language': 'en-GB', 'Content-Type': 'application/vnd.playlive.v1+json'}
    data = {'device': {'capabilities': [{'transport': 'DASH', 'protection': 'PLAYREADY', 'vcodec': 'H264', 'acodec': 'AAC', 'container': 'ISOBMFF'}], 'model': MODEL, 'maxVideoFormat': VIDEO_FORMAT, 'supportedColourSpaces': ['SDR'], 'hdcpEnabled': True}, 'client': {'thirdParties': ['FREEWHEEL', 'MEDIATAILOR', 'CONVIVA', 'FRV4'], 'timeShiftEnabled': False, 'variantCapable': True}, 'parentalControlPin': None, 'personaParentalControlRating': '5'}
    if playouts_type == 'event':
        data['providerVariantId'] = content_id
    else:
        data['serviceKey'] = content_id
    data = json.dumps(data)
    headers['x-sky-signature'] = calculate_signature('POST', url, headers, data)
    response = req.post(url, headers=headers, data=data)
    try:
        data = response.json()
        dvr_duration = data['asset']['streamVariants'][0]['dvrWindowDuration']['PT2H']['value']
        urls = [e['url'].split('?')[0].replace('$dvrWindowDuration$', dvr_duration) for e in data['asset']['endpoints']]
        lic_url = data['protection']['licenceAcquisitionUrl']
        return urls, lic_url
    except:
        return None, None

def do_cdm_external(pssh_b64, lic_url):
    try:
        pssh_obj = PSSH(pssh_b64)
        device_obj = Device.load(WVD_PATH)
        cdm_obj = Cdm.from_device(device_obj)
        session_id = cdm_obj.open()
        challenge_data = cdm_obj.get_license_challenge(session_id, pssh_obj.wrm_headers[0])
        lic_headers = {'Origin': 'https://www.nowtv.com', 'Referer': 'https://www.nowtv.com/', 'Content-Type': 'text/xml; charset=UTF-8', 'user-agent': 'okhttp/4.11.0'}
        licence = req.post(lic_url, headers=lic_headers, data=challenge_data)
        cdm_obj.parse_license(session_id, licence.text)
        keys = [f"{key.key_id.hex}:{key.key.hex()}" for key in cdm_obj.get_keys(session_id)]
        cdm_obj.close(session_id)
        return keys
    except Exception as e:
        print(f'CDM external failed: {e}', file=sys.stderr)
        return None

def do_action():
    get_token()
    
    if action == "login":
        login()
        sys.exit()
    
    if action == "channels":
        output = {'Channels': []}
        from datetime import timezone, timedelta
        tz_offset = timedelta(hours=2)
        tz = timezone(tz_offset)
        now = datetime.datetime.now(tz)
        minute = (now.minute // 5) * 5
        rounded_time = now.replace(minute=minute, second=0, microsecond=0)
        formatted_time = rounded_time.isoformat(timespec='minutes')
        headers = {'X-SkyOTT-Platform': PLATFORM, 'X-SkyOTT-ActiveTerritory': territory, 'X-SkyOTT-Provider': 'NOWTV', 'Referer': 'https://www.nowtv.com/', 'X-SkyOTT-Proposition': 'NOWOTT', 'X-SkyOTT-Language': 'en-GB', 'X-SkyOTT-Device': DEVICE, 'X-SkyOTT-Territory': territory, 'User-Agent': USER_AGENT}
        params = {'startTime': formatted_time, 'assetsPerChannelCapLimit': '4', 'playout_content_segments': 'NEWS,SHORTFORM,SPORTS,SPORTS_BONUS,SPORTS_CORE,SPORTS_ESSENTIALS,SPORTS_EVENTS,SSN', 'discovery_content_segments': 'ENTERTAINMENT,HAYU,KIDS,MOVIES,NEWS,SHORTFORM,SPORTS,SPORTS_CORE,SPORTS_ESSENTIALS,SPORTS_EVENTS,SPORTS_EVENTS_EXCLUSIVE,SSN'}
        response = req.get('https://web.clients.nowtv.com/bff/channel_guide', params=params, headers=headers)
        try:
            data = response.json()
            for ch in data.get('channels', []):
                channel = {
                    'Name': ch.get('name', 'Unknown'),
                    'Mode': 'live',
                    'SessionManifest': True,
                    'ManifestScript': f"id={ch.get('serviceKey', '')}",
                    'CdmType': 'playready',
                    'UseCdm': True,
                    'Cdm': f"id={ch.get('serviceKey', '')}",
                    'Video': 'best',
                    'OnDemand': True,
                    'SpeedUp': True,
                }
                output['Channels'].append(channel)
            print(json.dumps(output, indent=2))
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            return "error"
    
    elif action == "events":
        output = {'Events': []}
        print(json.dumps(output, indent=2))
    
    elif action == "heartbeat":
        sys.exit()
    
    elif action == "manifest":
        try:
            channel_id = id
            urls, lic_url = get_single(channel_id)
            if not urls:
                return "error"
            video_url = urls[0] if urls else ''
            output = {
                "Cdn": [{"Name": "default", "ManifestUrl": video_url}],
                "ManifestUrl": video_url,
                "Headers": {"Manifest": {'User-Agent': USER_AGENT}, "Media": {'User-Agent': USER_AGENT}},
                "Heartbeat": {"Url": '', "Params": '', "PeriodMs": 5*60*1000},
                "LicenseUrl": lic_url
            }
            print(json.dumps(output))
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            return "error"
    
    elif action == "cdm" and cdm == "external":
        try:
            channel_id = id
            urls, lic_url = get_single(channel_id)
            if lic_url and urls:
                pssh_to_use = pssh if pssh else get_pssh_from_mpd(urls[0])
                if pssh_to_use:
                    keys = do_cdm_external(pssh_to_use, lic_url)
                    if keys:
                        for key in keys:
                            print(key)
                    else:
                        return "error"
                else:
                    return "error"
            else:
                return "error"
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            return "error"
    
    else:
        print("invalid action: " + action, file=sys.stderr)

if do_action() == "error":
    login()
    do_action()
