#!/usr/bin/python3
import sys
import os
import base64
import json
import datetime
import pytz
import uuid
import secrets
import random
import string
import m3u8

# 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 pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.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)

if challenge == "cert":
    challenge = "CAQ="

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

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'

headers = {
    'accept': '*/*',
    'origin': 'https://www.nba.com',
    'referer': 'https://www.nba.com/',
    'user-agent': USER_AGENT,
}

token = ''
device_profile = ''

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 get_sts(nbaidentity):
    cookies = {'nbaidentity': nbaidentity}
    sts_headers = {**headers, 'content-type': 'application/json', 'x-client-platform': 'web'}
    response = req.get('https://identity.nba.com/api/v1/sts', headers=sts_headers, cookies=cookies)
    return response.json()['data']['AccessToken']

def register_device(tok):
    reg_headers = {**headers, 'authorizationtoken': tok, 'azukiimc': 'IMC7.5.0_AN_D3.0.0_S0', 'content-type': 'text/plain;charset=UTF-8', 'deviceprofile': device_profile}
    req.post('https://ottapp-appgw-amp.nba.com/v1/client/registrations', params={'ownerUid': 'azuki'}, headers=reg_headers, data='{}')

def login():
    global token, device_profile
    print("logging in...", file=sys.stderr)
    
    auth = get_auth()
    if not auth or 'nbaidentity' not in auth:
        print("No auth found. Please add NBA auth to auth file.", file=sys.stderr)
        print("Login to https://www.nba.com/ and copy 'nbaidentity' cookie value", file=sys.stderr)
        save_auth({'nbaidentity': 'ENTER_NBAIDENTITY_HERE', 'device_id': secrets.token_hex(16)})
        sys.exit(1)
    
    try:
        device_profile = base64.b64encode(json.dumps({'model': 'Desktop', 'osVersion': '10', 'vendorName': 'Microsoft', 'osName': 'HTML5', 'wvLevel': 'L3', 'deviceUUID': auth['device_id']}).encode('utf-8')).decode()
        token = get_sts(auth['nbaidentity'])
        register_device(token)
        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 random_country_code(exclude):
    exclude = {code.upper() for code in exclude}
    while True:
        code = ''.join(random.choices(string.ascii_uppercase, k=2))
        if code not in exclude:
            return code

def get_single(application_token, session_id, media_id, country_code):
    single_headers = {**headers, 'applicationtoken': application_token, 'authorizationtoken': token, 'azukiimc': 'IMC7.4.0_AN_D3.0.0_S0', 'content-type': 'text/plain;charset=UTF-8', 'deviceprofile': device_profile, 'X-LocationDetails': base64.b64encode(('{"PostalCode":"7000","CountryCode":"' + country_code + '"}').encode('utf-8')).decode('utf-8')}
    params = {'ownerUid': 'azuki', 'mediaId': media_id, 'sessionId': session_id, 'wave': '', 'enablelowlatency': 'true'}
    response = req.post('https://ottapp-appgw-amp.nba.com/v1/client/roll', params=params, headers=single_headers, data='{"roll":{"inhome":"no","rightsMode":2}}')
    try:
        data = response.json()
        manifest_uri = data['response']['manifest_uri']
        cdn = data['response']['cdns']['cdn'][0]['base_uri']
        location_info = json.loads(base64.b64decode(data['response']['personal_info']))
        url = cdn + '/' + manifest_uri
        for k, v in location_info.items():
            url += f'&{k}={v}'
        url += f'&sessionId={session_id}'
        return url
    except:
        return None

def do_action():
    get_token()
    
    if action == "login":
        login()
        sys.exit()
    
    if action == "channels":
        output = {'Channels': []}
        
        # Get linear channels
        try:
            response = req.get('https://ottapp-appgw-client.nba.com/catalogcache/s1-cache-channel-maps/1026', headers={**headers, 'content-type': 'application/json'})
            data = response.json()
            for ch in data.get('ServiceCollections', []):
                media_id = ch.get('SortName', '')
                app_token = ch.get('Id', '')
                geo_mode = ch.get('GeoPolicyMode', 'Allow')
                geo_locs = ch.get('GeoLocations', [])
                channel = {
                    'Name': ch.get('Name', 'Unknown'),
                    'Mode': 'live',
                    'SessionManifest': True,
                    'ManifestScript': f"id={media_id}&app={app_token}&geo={geo_mode}&locs={','.join(geo_locs)}",
                    'CdmType': 'widevine',
                    'UseCdm': True,
                    'Cdm': f"id={media_id}&app={app_token}",
                    'Video': 'best',
                    'OnDemand': True,
                    'SpeedUp': True,
                }
                output['Channels'].append(channel)
        except:
            pass
        
        # Get live games
        try:
            response = req.get('https://cdn.nba.com/static/json/liveData/channels/v2/channels_00.json', headers=headers)
            data = response.json()
            for g in data.get('channels', {}).get('games', []):
                if g.get('gameStatus') == 2:
                    for s in g.get('streams', []):
                        media_id = s.get('productionId', '')
                        geo_mode = s.get('GeoPolicyMode', 'Allow')
                        geo_locs = s.get('GeoLocations', [])
                        channel = {
                            'Name': s.get('title', 'Unknown'),
                            'Mode': 'live',
                            'SessionManifest': True,
                            'ManifestScript': f"id={media_id}&game={g.get('gameId', '')}&geo={geo_mode}&locs={','.join(geo_locs)}",
                            'CdmType': 'widevine',
                            'UseCdm': True,
                            'Cdm': f"id={media_id}",
                            'Video': 'best',
                            'OnDemand': True,
                            'SpeedUp': True,
                        }
                        output['Channels'].append(channel)
        except:
            pass
        
        print(json.dumps(output, indent=2))
    
    elif action == "events":
        output = {'Events': []}
        print(json.dumps(output, indent=2))
    
    elif action == "heartbeat":
        sys.exit()
    
    elif action == "manifest":
        try:
            params_dict = {}
            for param in id.split('&'):
                if '=' in param:
                    k, v = param.split('=', 1)
                    params_dict[k] = v
            
            media_id = params_dict.get('id', '')
            app_token = params_dict.get('app', '')
            geo_mode = params_dict.get('geo', 'Allow')
            geo_locs = params_dict.get('locs', '').split(',') if params_dict.get('locs') else []
            
            if geo_mode == 'Allow' and geo_locs:
                country_code = geo_locs[0]
            else:
                country_code = random_country_code(geo_locs)
            
            session_id = str(uuid.uuid4())
            video_url = get_single(app_token, session_id, media_id, country_code)
            if not video_url:
                return "error"
            
            output = {
                "Cdn": [{"Name": "default", "ManifestUrl": video_url}],
                "ManifestUrl": video_url,
                "Headers": {"Manifest": {'User-Agent': USER_AGENT, 'Origin': 'https://www.nba.com', 'Referer': 'https://www.nba.com/'}, "Media": {'User-Agent': USER_AGENT}},
                "Heartbeat": {"Url": '', "Params": '', "PeriodMs": 5*60*1000}
            }
            print(json.dumps(output))
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            return "error"
    
    elif action == "cdm":
        print("NBA CDM requires complex session handling - use external CDM", file=sys.stderr)
    
    else:
        print("invalid action: " + action, file=sys.stderr)

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