#!/usr/bin/python3
import sys
import os
import json
import requests
import logging
import secrets
import base64
import re
from datetime import datetime, timezone
from bs4 import BeautifulSoup
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH

# ===============================
# KONFIGURACIJA LOGGING-A
# ===============================
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stderr)
    ]
)

# ===============================
# KONFIG KONSTANTE
# ===============================

# Putanja do WVD fajla (po potrebi promijeni)
WVD_PATH = "/home/o11/scripts/WVD.wvd"

# ===============================
# O11 PARAMETRI
# ===============================
def parse_param(name):
    for arg in sys.argv:
        if arg.startswith(name + "="):
            return arg.split("=", 1)[1]
    return ""

action = parse_param("action")
bind = parse_param("bind")
proxy_param = parse_param("proxy")  # više se ne koristi (nema proxyja), ali može ostati
worker = parse_param("worker")
account = parse_param("account")
password = parse_param("password")

# dodatno za CDM, isto kao u Yettel skripti
cdm_mode = parse_param("cdm")          # internal / external
pssh_param = parse_param("pssh")       # PSSH koji O11 proslijedi
challenge_param = parse_param("challenge")

# channel id dolazi iz O11: id=11
CHANNEL_ID = parse_param("id") or "716"

# ===============================
# DEFAULT LOGIN PODACI
# ===============================
DEFAULT_USERNAME = "dinoze2021@gmail.com"
DEFAULT_PASSWORD = "SamoA12026!"

# ===============================
# A1 LOGIN FUNKCIJE
# ===============================
def do_login(username, password, device_id='', refresh=False):
    logging.info("Login na A1 za korisnika: %s", username)
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Content-Type': 'application/json',
        'Accept': '*/*',
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'Accept-Encoding': 'gzip, deflate, br'
    }

    params = {
        'devId': device_id,
        'user': username,
        'pwd': password,
        'rqT': 'true',
        'refr': 'false',
    }

    if device_id and refresh:
        params['refr'] = 'true'
        logging.info("Login s device_id (refresh): %s", device_id)
    elif device_id and not refresh:
        logging.info("Login s device_id (prvi put): %s", device_id)

    try:
        response = requests.post(
            'https://web.a1xploretv.hr:8843/ext_dev_facade/auth/Login',
            headers=headers,
            params=params,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()

        if 'token' not in data:
            logging.error("A1 login error: %s", data)
            if data.get('error_code') == 'errClDevNotFound':
                return None, "device_not_found"
            raise Exception("A1 login failed")

        logging.info("Login OK, token dobijen")
        return data['token'], "success"
    except Exception as e:
        logging.error("Login failed: %s", e)
        return None, "error"

def get_device_id(token):
    logging.info("Dohvaćam device_id")
    cookies = {
        'webfw-1-auth': token,
    }

    headers = {
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'SDSEVO_DEVICE_ID': 'none',
        'SDSEVO_SESSION_ID': '[sedt=1]' + token,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null',
        'accept': '*/*',
        'content-type': 'application/json',
    }

    json_data = {
        'operationName': 'limitedSetup',
        'variables': {},
        'query': 'query limitedSetup {\n  me {\n    id\n    firstName\n    household {\n      id\n      community {\n        id\n        title\n        description\n        __typename\n      }\n      devices {\n        id\n        items {\n          id\n          language {\n            id\n            __typename\n          }\n          clientGeneratedId\n          deviceType\n          renameable\n          removable\n          removableFrom\n          registrationTime\n          name\n          __typename\n        }\n        __typename\n      }\n      maxNumberOfUnmanagedDevices\n      __typename\n    }\n    __typename\n  }\n}\n',
    }

    try:
        response = requests.post(
            'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
            cookies=cookies,
            headers=headers,
            json=json_data,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()

        devices = data['data']['me']['household']['devices']['items']
        device_id = None
        
        # Prvo probaj pronaći PC uređaj
        for d in devices:
            if 'PC' in d['deviceType']:
                device_id = d['id']
                logging.info("Pronađen PC device: %s", device_id)
                break
        
        # Ako nema PC uređaja, uzmi prvi dostupni
        if device_id is None and devices:
            device_id = devices[0]['id']
            logging.info("Koristim prvi dostupni device: %s", device_id)
        
        # Ako nema nijednog uređaja, generiraj novi
        if device_id is None:
            device_id = secrets.token_hex(16)
            logging.info("Generiran novi device_id: %s", device_id)

        logging.info("device_id=%s", device_id)
        return device_id
    except Exception as e:
        logging.error("Greška u get_device_id: %s", e)
        # Generiraj random device_id kao fallback
        return secrets.token_hex(16)

def get_bearer_cookies(token, device_id):
    logging.info("Dohvaćam bearer cookies za device_id: %s", device_id)
    cookies = {
        'webfw-1-auth': token,
    }

    headers = {
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'SDSEVO_DEVICE_ID': device_id,
        'SDSEVO_SESSION_ID': '[sedt=0]' + token,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
        'Zappware-User-Agent': 'windows_pc_chrome/v30.0.0 (Nexx 4.0 windows_pc_chrome; Windows; 10) null',
        'accept': '*/*',
        'content-type': 'application/json',
    }

    try:
        # Prvo pokušaj s stopPlayback mutacijom
        json_data = {
            'operationName': 'stopPlayback',
            'variables': {
                'input': {
                    'sessionId': '',
                },
            },
            'query': 'mutation stopPlayback($input: StopPlaybackInput!) {\n  stopPlayback(input: $input) {\n    success\n    __typename\n  }\n}\n',
        }

        response = requests.post(
            'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
            headers=headers,
            json=json_data,
            cookies=cookies,
            timeout=10
        )
        
        # Kombiniraj inicijalne kolačiće sa onima iz odgovora
        all_cookies = {**cookies, **response.cookies.get_dict()}
        logging.info("Bearer cookies uspješno dobijeni, ukupno: %d", len(all_cookies))
        return all_cookies
    except Exception as e:
        logging.warning("Greška u get_bearer_cookies sa stopPlayback: %s, pokušavam alternativni upit", e)
        
        # Fallback: probaj s jednostavnijim upitom
        try:
            json_data_fallback = {
                'operationName': 'getProfiles',
                'variables': {},
                'query': 'query getProfiles {\n  me {\n    id\n    firstName\n    device {\n      id\n      clientGeneratedId\n      deviceType\n      __typename\n    }\n    __typename\n  }\n}\n',
            }
            
            response_fallback = requests.post(
                'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
                headers=headers,
                json=json_data_fallback,
                cookies=cookies,
                timeout=10
            )
            
            all_cookies = {**cookies, **response_fallback.cookies.get_dict()}
            logging.info("Bearer cookies dobijeni preko fallback upita, ukupno: %d", len(all_cookies))
            return all_cookies
        except Exception as e2:
            logging.error("Greška i u fallback get_bearer_cookies: %s", e2)
            return cookies  # Vrati barem osnovne kolačiće

def get_profile_id(cookies):
    logging.info("Dohvaćam profile_id")
    headers = {
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
        'Zappware-User-Agent': 'windows_pc_chrome/v30.0.0 (Nexx 4.0 windows_pc_chrome; Windows; 10) null',
        'accept': '*/*',
        'content-type': 'application/json',
    }

    json_data = {
        'operationName': 'getProfiles',
        'variables': {},
        'query': 'query getProfiles {\n  me {\n    ...cacheInfoFragment\n    firstName\n    device {\n      ...cacheInfoFragment\n      clientGeneratedId\n      deviceType\n      eventLoggingOptions\n      quickGuideVideo {\n        id\n        title\n        entitlements {\n          ...VODAssetEntitlementsFragment\n          __typename\n        }\n        __typename\n      }\n      language {\n        id\n        code\n        __typename\n      }\n      language {\n        ...cacheInfoFragment\n        title\n        code\n        __typename\n      }\n      activeProfile {\n        ...cacheInfoFragment\n        name\n        avatar {\n          ...cacheInfoFragment\n          type\n          __typename\n        }\n        kind\n        preferences {\n          ...cacheInfoFragment\n          appSorting\n          displayNotSubscribedChannels\n          firstAudioLanguage\n          secondAudioLanguage\n          firstSubtitleLanguage\n          secondSubtitleLanguage\n          hardOfHearing\n          visuallyImpaired\n          trackViewingBehaviour\n          consents {\n            type\n            accepted\n            __typename\n          }\n          __typename\n        }\n        permissions {\n          ...cacheInfoFragment\n          parentalRating {\n            ...cacheInfoFragment\n            adult\n            description\n            title\n            rank\n            icon(width: 10, height: 10, flavour: NORMAL) {\n              ...cacheInfoFragment\n              url\n              __typename\n            }\n            __typename\n          }\n          maskContent\n          canPurchase\n          purchaseProtection\n          purchaseLimit\n          displayBlockedChannels\n          otherProfilesContent\n          editMyLibrary\n          createRecordings\n          logoutPincode\n          editChannelLists\n          singleChannelList {\n            ...cacheInfoFragment\n            name\n            __typename\n          }\n          accessSearch\n          resortApps\n          manageApps\n          __typename\n        }\n        onboardingInfo {\n          ...cacheInfoFragment\n          ageRatingStepCompleted\n          privacyStepCompleted\n          __typename\n        }\n        __typename\n        }\n      __typename\n    }\n    household {\n      ...cacheInfoFragment\n      onboardingInfo {\n        id\n        masterPincodeStepCompleted\n        communityStepCompleted\n        privacyStepCompleted\n        replayStepCompleted\n        profilesStepCompleted\n        __typename\n      }\n      maxNumberOfConfirmedReplayChannels\n      displayNonAdultContent\n      agreedToTermsAndConditions\n      community {\n        id\n        title\n        description\n        __typename\n      }\n      profiles {\n        ...cacheInfoFragment\n        items {\n          ...cacheInfoFragment\n          name\n          avatar {\n            id\n            expiry\n            type\n            __typename\n          }\n          kind\n          protection\n          pincode\n          permissions {\n            ...cacheInfoFragment\n            parentalRating {\n              ...cacheInfoFragment\n              adult\n              description\n              title\n              rank\n              icon(width: 10, height: 10, flavour: NORMAL) {\n                ...cacheInfoFragment\n                url\n                __typename\n              }\n              __typename\n            }\n            maskContent\n            canPurchase\n            purchaseProtection\n            purchaseLimit\n            displayBlockedChannels\n            otherProfilesContent\n            editMyLibrary\n            createRecordings\n            logoutPincode\n            editChannelLists\n            singleChannelList {\n              ...cacheInfoFragment\n              name\n              __typename\n            }\n            accessSearch\n            resortApps\n            manageApps\n            __typename\n          }\n          preferences {\n            ...cacheInfoFragment\n            appSorting\n            displayNotSubscribedChannels\n            firstAudioLanguage\n            secondAudioLanguage\n            firstSubtitleLanguage\n            secondSubtitleLanguage\n            hardOfHearing\n            visuallyImpaired\n            trackViewingBehaviour\n            consents {\n              type\n              accepted\n              __typename\n            }\n            __typename\n          }\n          onboardingInfo {\n            ...cacheInfoFragment\n            ageRatingStepCompleted\n            privacyStepCompleted\n            __typename\n          }\n          __typename\n        }\n        __typename\n      }\n      devices {\n        id\n        items {\n          id\n          language {\n            id\n            __typename\n          }\n          clientGeneratedId\n          deviceType\n          renameable\n          removable\n          removableFrom\n          registrationTime\n          name\n          __typename\n        }\n        __typename\n      }\n      maxNumberOfUnmanagedDevices\n      __typename\n    }\n    __typename\n  }\n}\n\nfragment cacheInfoFragment on Cacheable {\n  __typename\n  id\n  expiry\n}\n\nfragment VODAssetEntitlementsFragment on VODAssetEntitlementCatalog {\n  ...cacheInfoFragment\n  itemCount\n  items {\n    id\n    playback\n    playbackAvailableUntil\n    product {\n      ...cacheInfoFragment\n      kind\n      videoQuality\n      purchaseInfo {\n        ...cacheInfoFragment\n        status\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n  __typename\n}\n',
    }

    try:
        response = requests.post(
            'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
            cookies=cookies,
            headers=headers,
            json=json_data,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()
        
        # Pokušaj pronaći profile_id u različitim strukturama
        if 'data' in data and 'me' in data['data']:
            me_data = data['data']['me']
            if 'device' in me_data and 'activeProfile' in me_data['device']:
                profile_id = me_data['device']['activeProfile']['id']
                logging.info("profile_id=%s", profile_id)
                return profile_id
            elif 'household' in me_data and 'profiles' in me_data['household']:
                # Ako nema activeProfile, uzmi prvi profil iz householda
                profiles = me_data['household']['profiles']['items']
                if profiles:
                    profile_id = profiles[0]['id']
                    logging.info("profile_id (prvi iz householda)=%s", profile_id)
                    return profile_id
        
        logging.warning("Profile ID nije pronađen u očekivanoj strukturi")
        return None
    except Exception as e:
        logging.error("Greška u get_profile_id: %s", e)
        return None

# ===============================
# FUNKCIJA ZA DOHVAĆANJE KANALA
# ===============================
def get_channels(cookies, profile_id):
    logging.info("Dohvaćam listu A1 kanala za profile_id=%s", profile_id)
    headers = {
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null',
        'accept': '*/*',
        'content-type': 'application/json',
    }

    current_time_utc = datetime.now(timezone.utc)
    formatted_time = current_time_utc.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'

    json_data = {
        'operationName': 'liveTV',
        'variables': {
            'profileId': profile_id,
            'channelListId': '36-17754',
            'channelAfterCursor': None,
            'firstChannels': 300,
            'currentTime': formatted_time,
            'logoWidth': 76,
            'logoHeight': 28,
            'thumbnailHeight': 280,
            'backgroundHeight': 780,
            'backgroundWidth': 1920,
        },
        'query': 'query liveTV($profileId: ID!, $channelAfterCursor: String, $firstChannels: Int!, $currentTime: Date!, $logoWidth: Int!, $logoHeight: Int!, $logoFlavour: ImageFlavour, $thumbnailHeight: Int!, $backgroundHeight: Int!, $backgroundWidth: Int!, $channelListId: ID!) {\n  channelList(id: $channelListId) {\n    ...cacheInfoFragment\n    name\n    channels(first: $firstChannels, after: $channelAfterCursor) {\n      ...cacheInfoFragment\n      totalCount\n      pageInfo {\n        hasNextPage\n        endCursor\n        __typename\n      }\n      edges {\n        ...cacheInfoFragment\n        cursor\n        node {\n          ...cacheInfoFragment\n          parentalRating {\n            ...cacheInfoFragment\n            rank\n            adult\n            __typename\n          }\n          title\n          userInfo {\n            ...cacheInfoFragment\n            subscribed\n            __typename\n          }\n          personalInfo(profileId: $profileId) {\n            ...cacheInfoFragment\n            blocked\n            __typename\n          }\n          logo(width: $logoWidth, height: $logoHeight, flavour: $logoFlavour) {\n            ...cacheInfoFragment\n            url\n            __typename\n          }\n          currentEvent: eventsAt(time: $currentTime, previous: 0, following: 0) {\n            ...cacheInfoFragment\n            itemCount\n            items {\n              ...nowPlayingEventFragment\n              __typename\n            }\n            __typename\n          }\n          __typename\n        }\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n}\n\nfragment cacheInfoFragment on Cacheable {\n  __typename\n  id\n  expiry\n}\n\nfragment nowPlayingEventFragment on Event {\n  ...cacheInfoFragment\n  ...eventInfoBasicFragment\n  eventEntitlements: entitlements {\n    ...eventEntitlementsFragment\n    __typename\n  }\n  personalEventInfo: personalInfo(profileId: $profileId) {\n    ...personalEventInfoFragment\n    __typename\n  }\n  __typename\n}\n\nfragment eventInfoBasicFragment on Event {\n  title\n  start\n  end\n  blackout\n  startOverTVBeforeTime\n  startOverTVAfterTime\n  ppv\n  channel {\n    id\n    title\n    beforeTime\n    afterTime\n    __typename\n  }\n  thumbnail(height: $thumbnailHeight) {\n    ...imageFragment\n    __typename\n  }\n  parentalRating {\n    ...parentalRatingFragment\n    __typename\n  }\n  backgroundImage(width: $backgroundWidth, height: $backgroundHeight) {\n    ...imageFragment\n    __typename\n  }\n  metadata {\n    id\n    title\n    originalTitle\n    genre {\n      id\n      title\n      __typename\n    }\n    episodeInfo {\n      id\n      title\n      number\n      season\n      __typename\n    }\n    seriesInfo {\n      id\n      title\n      __typename\n    }\n    __typename\n  }\n  __typename\n}\n\nfragment imageFragment on Image {\n  id\n  url\n  width\n  height\n  __typename\n}\n\nfragment parentalRatingFragment on ParentalRating {\n  ...cacheInfoFragment\n  title\n  description\n  rank\n  adult\n  __typename\n}\n\nfragment eventEntitlementsFragment on EventEntitlements {\n  pauseLiveTV\n  restartTV\n  catchupTV\n  catchupTVAvailableUntil\n  networkRecording\n  networkRecordingPlannableUntil\n  ppvTV\n  needsConcurrencyToken\n  __typename\n}\n\nfragment personalEventInfoFragment on PersonalEventInfo {\n  ...cacheInfoFragment\n  recordings(kindFilter: NETWORK) {\n    ... on NetworkRecording {\n      id\n      start\n      end\n      status\n      availableUntil\n      conflictAutoResolution\n      allowArchiving\n      channel {\n        ...cacheInfoFragment\n        title\n        kind\n        parentalRating {\n          ...parentalRatingFragment\n          __typename\n        }\n        __typename\n      }\n      personalRecordingInfo: personalInfo(profileId: $profileId) {\n        ...personalRecordingInfoFragment\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n  bookmark {\n    ...bookmarkFragment\n    __typename\n  }\n  __typename\n}\n\nfragment bookmarkFragment on Bookmark {\n  ...cacheInfoFragment\n  position\n  audio\n  subtitle\n  __typename\n}\n\nfragment personalRecordingInfoFragment on PersonalRecordingInfo {\n  id\n  bookmark {\n    ...cacheInfoFragment\n    position\n    __typename\n  }\n  partOfSeriesRecording\n  seasonCancelled\n  seriesCancelled\n  archived\n  unarchiveUntil\n  __typename\n}\n',
    }

    try:
        response = requests.post(
            'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
            cookies=cookies,
            headers=headers,
            json=json_data,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()
        channels = []
        
        if 'data' in data and 'channelList' in data['data']:
            edges = data['data']['channelList']['channels']['edges']
            for e in edges:
                channels.append(e['node'])
        
        logging.info("A1 kanala dobijeno: %d", len(channels))
        if channels:
            titles = [c['title'] for c in channels[:5]]
            logging.info("Prvih 5 A1 kanala: %s", titles)
        return channels
    except Exception as e:
        logging.error("Greška u get_channels: %s", e)
        if 'response' in locals():
            logging.error("Response status: %s", response.status_code)
            logging.error("Response text: %s", response.text[:500] if hasattr(response, 'text') else 'N/A')
        return []

# ===============================
# FUNKCIJE ZA PSSH & CDM (KEY/KID)
# ===============================
def extract_pssh_from_mpd(xml_bytes):
    try:
        soup = BeautifulSoup(xml_bytes, features="xml")

        psshes = []
        for tag in soup.find_all():
            tn = (tag.name or "").lower()
            if tn.endswith("pssh"):
                if tag.string and tag.string.strip():
                    psshes.append(tag.string.strip())

        if psshes:
            return psshes

        m = re.findall(
            rb"<(?:[\w\-]+:)?pssh>([^<]+)</(?:[\w\-]+:)?pssh>",
            xml_bytes,
            flags=re.IGNORECASE
        )
        if m:
            return [x.decode().strip() for x in m]
    except Exception:
        pass

    return []

def find_wv_pssh_offsets(raw: bytes) -> list:
    offsets = []
    offset = 0
    while True:
        offset = raw.find(b'pssh', offset)
        if offset == -1:
            break
        size = int.from_bytes(raw[offset - 4:offset], byteorder='big')
        pssh_offset = offset - 4
        offsets.append(raw[pssh_offset:pssh_offset + size])
        offset += size
    return offsets

def to_pssh(content: bytes) -> list:
    logging.info("Konvertujem init segment u PSSH")
    wv_offsets = find_wv_pssh_offsets(content)
    pssh_list = [base64.b64encode(wv_offset).decode() for wv_offset in wv_offsets]
    logging.info("PSSH lista: %s", pssh_list)
    return pssh_list

def init_to_pssh(init_url):
    logging.info("Dohvaćam init segment: %s", init_url)
    headers = {
        'accept': '*/*',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
        'content-type': 'application/octet-stream',
    }

    try:
        response = requests.get(init_url, headers=headers, timeout=10)
        response.raise_for_status()
        pssh_list = to_pssh(response.content)
        logging.info("Init segment obrađen.")
        return pssh_list
    except Exception as e:
        logging.error("Greška pri dohvaćanju init segmenta: %s", e)
        return []

def get_pssh(url):
    logging.info("Vadim PSSH iz MPD: %s", url)
    headers = {
        'Accept': '*/*',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    }

    try:
        resp = requests.get(url, headers=headers, timeout=10)
        resp.raise_for_status()
        location = resp.url
        logging.info("Finalni MPD URL (poslije redirecta): %s", location)

        # prvo probaj direktno iz MPD
        psshes = extract_pssh_from_mpd(resp.content)
        if psshes:
            logging.info("Nađen PSSH u MPD: %s", psshes)
            return psshes[-1], location

        # fallback: iz init segmenta
        try:
            soup = BeautifulSoup(resp.content, features="xml")
            seg_tmpl = soup.find('SegmentTemplate')
            rep = soup.find('Representation')

            if seg_tmpl and rep:
                init_template = seg_tmpl.get('initialization')
                bandwidth = rep.get('bandwidth')
                rep_id = rep.get('id')

                if init_template and (bandwidth or rep_id):
                    parts = location.split('/')
                    parts.pop()
                    base = '/'.join(parts)

                    init_url = init_template
                    if bandwidth:
                        init_url = init_url.replace('$Bandwidth$', bandwidth)
                    if rep_id:
                        init_url = init_url.replace('$RepresentationID$', rep_id)

                    init_url = f"{base}/{init_url}"
                    logging.info("Init URL: %s", init_url)

                    pssh_list = init_to_pssh(init_url)
                    if pssh_list:
                        logging.info("Nađen PSSH u init segmentu: %s", pssh_list)
                        return pssh_list[-1], location
        except Exception as e2:
            logging.warning("Init fallback nije uspio: %s", e2)

        logging.info("Nema PSSH – nastavljam bez ključeva.")
        return None, location

    except Exception as e:
        logging.error("Greška u get_pssh: %s", e)
        try:
            return None, resp.url
        except Exception:
            return None, None

def do_cdm(pssh, device_id):
    """
    Widevine CDM – vrati listu 'kid:key' stringova
    """
    logging.info("Pokrećem CDM za PSSH")
    try:
        if not os.path.exists(WVD_PATH):
            logging.error('WVD.wvd fajl ne postoji: %s', WVD_PATH)
            return None

        pssh_obj = PSSH(pssh)
        device = Device.load(WVD_PATH)
        cdm = Cdm.from_device(device)
        session_id = cdm.open()
        challenge = cdm.get_license_challenge(session_id, pssh_obj)

        headers = {
            'Origin': 'https://go.a1xploretv.hr',
            'Referer': 'https://go.a1xploretv.hr/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Content-Type': 'application/x-www-form-urlencoded',
        }

        licence = requests.post(
            'https://vipottvmxdrmwv.vip.hr/?deviceId=' + device_id,
            headers=headers,
            data=challenge,
            timeout=10
        )
        licence.raise_for_status()
        logging.info("License uspješno dobijen")

        cdm.parse_license(session_id, licence.content)
        keys = []
        for key in cdm.get_keys(session_id):
            if key.type != 'SIGNING':
                keys.append(f"{key.kid.hex}:{key.key.hex()}")

        cdm.close(session_id)
        logging.info("Ključevi izvučeni: %s", keys)
        return keys

    except Exception as e:
        logging.error("Greška pri dohvaćanju license/CDM: %s", e)
        return None

def split_kid_key_list(drm_keys):
    """
    Uzmi listu 'kid:key' stringova i vrati (kid, key) za prvi element.
    """
    if not drm_keys:
        return None, None
    first = drm_keys[0]
    if ":" not in first:
        return None, None
    kid, key = first.split(":", 1)
    return kid, key

def build_key_objects(drm_keys):
    """
    Pretvori listu 'kid:key' stringova u listu objekata:
      [{ "kid": "...", "key": "..." }, ...]
    Ovo je format koji widevine external CDM najčešće očekuje.
    """
    objs = []
    if not drm_keys:
        return objs
    for item in drm_keys:
        if ":" not in item:
            continue
        kid, key = item.split(":", 1)
        objs.append({
            "kid": kid,
            "key": key
        })
    return objs

# ===============================
# FUNKCIJA ZA DOHVAĆANJE MPD (MANIFEST)
# ===============================
def get_mpd(cookies, profile_id, channel_id):
    logging.info("Dohvaćam MPD za channel_id=%s", channel_id)
    headers = {
        'Origin': 'https://go.a1xploretv.hr',
        'Referer': 'https://go.a1xploretv.hr/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null',
        'accept': '*/*',
        'content-type': 'application/json',
    }

    json_data = {
        'operationName': 'playChannel',
        'variables': {
            'input': {
                'channelId': channel_id,
                'replaceSessionId': None,
            },
            'profileId': profile_id,
        },
        'query': 'mutation playChannel($input: PlayChannelInput!, $profileId: ID!) {\n  playChannel(input: $input) {\n    playbackInfo {\n      sessionId\n      url\n      playbackRestrictions {\n        ... on EventConcurrencyPlaybackRestriction {\n          message\n          __typename\n        }\n        __typename\n      }\n      adPlaybackRestrictions\n      trickplayRestrictions\n      adSignallingType\n      channel {\n        id\n        kind\n        personalInfo(profileId: $profileId) {\n          id\n          audioLanguage\n          subtitleLanguage\n          __typename\n        }\n        __typename\n      }\n      heartbeat {\n        ... on HttpHeartbeat {\n          url\n          interval\n          includeAuthHeaders\n          __typename\n        }\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n}\n',
    }

    try:
        response = requests.post(
            'https://web.a1xploretv.hr:8443/sdsmiddleware/Vipnet/graphql/4.0',
            cookies=cookies,
            headers=headers,
            json=json_data,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()
        
        if 'data' in data and 'playChannel' in data['data']:
            playback_info = data['data']['playChannel']['playbackInfo']
            logging.info("MPD dobijen, url=%s", playback_info['url'])
            return playback_info['url'], playback_info.get('sessionId')
        
        logging.error("Neočekivana struktura odgovora u get_mpd")
        return None, None
    except Exception as e:
        logging.error("Greška u get_mpd: %s", e)
        return None, None

# ===============================
# FUNKCIJA ZA O11 CHANNELS FORMAT
# ===============================
def get_channels_o11_format(channels_data):
    """
    Vraća kanale u formatu koji O11 očekuje.
    Za A1 koristimo INTERNAL widevine, kao kod Infokanala:
      - UseCdm = True
      - CdmType = "widevine"
      - CdmMode = "internal"
    Skripta će preko MANIFEST akcije vraćati URL sa decryption_key=KID:KEY.
    """
    o11_channels = []
    
    for channel in channels_data:
        try:
            channel_id = channel.get("id", "")
            channel_name = channel.get("title", "")
            
            if not channel_id or not channel_name:
                continue

            logo_url = ""
            if channel.get("logo") and isinstance(channel.get("logo"), dict):
                logo_url = channel.get("logo", {}).get("url", "")
            
            channel_info = {
                "Name": channel_name,
                "Mode": "live",
                "SessionManifest": True,
                "ManifestScript": f"id={channel_id}",

                # DRM / CDM – INTERNAL widevine (isto kao Infokanal)
                "UseCdm": True,
                "CdmType": "widevine",
                "CdmMode": "internal",

                "Video": "best",
                "OnDemand": True,
                "SpeedUp": True,
                "Logo": logo_url
            }
            
            o11_channels.append(channel_info)
            
        except Exception as e:
            logging.error("Greška pri obradi kanala: %s", e)
            continue
    
    logging.info("Kanala pripremljeno za O11: %d", len(o11_channels))
    return {"Channels": o11_channels}

# ===============================
# FUNKCIJA ZA DOHVAĆANJE SESIJE
# ===============================
def get_a1_session(username=None, password=None):
    """Dobiva sesiju za A1 i vraća kolačiće i profile_id"""
    
    login_username = username if username else DEFAULT_USERNAME
    login_password = password if password else DEFAULT_PASSWORD
    
    logging.info("Pokrećem A1 autentikaciju za korisnika: %s", login_username)
    
    # 1. Prvi login bez device_id
    token, status = do_login(login_username, login_password)
    if not token:
        logging.error("Neuspješan prvi login")
        return None
    
    # 2. Dohvati device_id
    device_id = get_device_id(token)
    
    # 3. Pokušaj login s device_id, ali bez refresh parametra (prvi put)
    token2, status2 = do_login(login_username, login_password, device_id, refresh=False)
    if token2:
        logging.info("Drugi login sa device_id uspio – koristim novi token")
        token = token2
    else:
        logging.warning("Drugi login sa device_id nije uspio (status=%s) – nastavljam sa prvim tokenom", status2)
        if status2 == "device_not_found":
            logging.warning("Device nije pronađen, pokušavam još jednom bez device_id...")
            token3, status3 = do_login(login_username, login_password)
            if token3:
                logging.info("Fallback login bez device_id uspio – koristim token iz fallback-a")
                token = token3
            else:
                logging.error("Neuspješan login nakon fallbacka bez device_id")
                return None
    
    # 4. Dohvati bearer cookies
    cookies = get_bearer_cookies(token, device_id)
    if not cookies:
        logging.error("Neuspješno dobivanje cookies")
        return None
    
    # 5. Dohvati profile_id
    profile_id = get_profile_id(cookies)
    if not profile_id:
        logging.warning("Profile ID nije pronađen, koristim default_profile")
        profile_id = "default_profile"
    
    logging.info("Autentikacija uspješna: device_id=%s, profile_id=%s", device_id, profile_id)
    
    session_data = {
        'cookies': cookies,
        'profile_id': profile_id,
        'token': token,
        'device_id': device_id
    }
    
    return session_data

# ===============================
# FUNKCIJA ZA AKCIJU "channels" 
# ===============================
def handle_channels_action():
    """Rukuje O11 channels akcijom"""
    
    login_username = account if account else DEFAULT_USERNAME
    login_password = password if password else DEFAULT_PASSWORD
    
    logging.info("Pokrećem channels akciju za korisnika: %s", login_username)
    
    session_data = get_a1_session(login_username, login_password)
    if not session_data:
        logging.error("Neuspješno dobivanje sesije")
        return {"Channels": []}
    
    channels_data = get_channels(session_data['cookies'], session_data['profile_id'])
    if not channels_data:
        logging.error("Nema kanala dobivenih iz API-ja")
        return {"Channels": []}
    
    output = get_channels_o11_format(channels_data)
    
    if output["Channels"]:
        first_few = [ch["Name"] for ch in output["Channels"][:5]]
        logging.info("Prvih 5 kanala: %s", first_few)
        logging.info("Ukupno kanala: %d", len(output["Channels"]))
    
    return output

# ===============================
# FUNKCIJA ZA AKCIJU "manifest"
# ===============================
def handle_manifest_action():
    """
    Rukuje O11 manifest akcijom – vrati:
      - ManifestUrl (sa decryption_key=KID:KEY ako imamo)
      - Keys/keys (lista objekata {kid, key})
      - Kid/kid, Key/key (prvi par)
    Ako je iz nekog razloga samo URL imao decryption_key,
    parsiramo ga nazad i punimo polja iz URL-a.
    """
    login_username = account if account else DEFAULT_USERNAME
    login_password = password if password else DEFAULT_PASSWORD
    
    logging.info("Pokrećem manifest akciju za kanal: %s", CHANNEL_ID)
    
    session_data = get_a1_session(login_username, login_password)
    if not session_data:
        logging.error("Neuspješno dobivanje sesije")
        return {"Error": "Authentication failed"}
    
    device_id = session_data['device_id']
    
    # 1) Dobavi MPD/HLS URL
    manifest_url, session_id = get_mpd(session_data['cookies'], session_data['profile_id'], CHANNEL_ID)
    if not manifest_url:
        logging.error("Nema manifest URL-a")
        return {"Error": "Manifest not found"}
    
    logging.info("Manifest URL dobiven: %s", manifest_url)
    
    # 2) Izvuci PSSH + KEY/KID preko CDM-a (DASH)
    final_url = manifest_url
    drm_keys = None
    
    pssh, final_url = get_pssh(manifest_url)
    if pssh:
        logging.info("PSSH pronađen, pokušavam izvući ključeve...")
        drm_keys = do_cdm(pssh, device_id)
    else:
        logging.info("PSSH nije pronađen, preskačem CDM")

    kid = None
    key = None
    if drm_keys:
        kid, key = split_kid_key_list(drm_keys)
        logging.info("Prvi KID=%s, KEY=%s", kid, key)

    # 3) Ako imamo ključeve – dodaj ih u URL kao decryption_key=KID:KEY,...
    if drm_keys:
        logging.info("Dodajem %d ključeva u decryption_key URL parametar", len(drm_keys))
        sep = "&" if "?" in final_url else "?"
        final_url = final_url + sep + "decryption_key=" + ",".join(drm_keys)
    else:
        logging.info("Nema ključeva iz CDM-a – šaljem čisti manifest URL (bez decryption_key)")

    # 3b) Ako slučajno URL već sadrži decryption_key (od A1 ili od nas),
    #     a nemamo drm_keys listu, probaj iz URL-a izvući KID:KEY natrag.
    if not drm_keys:
        m = re.search(r"[?&]decryption_key=([^&]+)", final_url)
        if m:
            raw_keys = m.group(1)
            drm_keys = raw_keys.split(",")
            kid, key = split_kid_key_list(drm_keys)
            logging.info("Ključevi preuzeti iz decryption_key parametra u URL-u: %s", drm_keys)

    # 4) Sastavi output JSON
    output = {
        "Cdn": [],
        "ManifestUrl": final_url,
        "Headers": {
            "Manifest": {"User-Agent": "Mozilla/5.0"},
            "Media": {"User-Agent": "Mozilla/5.0"}
        },
        "Heartbeat": {"Url": "", "Params": "", "PeriodMs": 300000}
    }

    # DODATNO: vrati ključeve u JSON-u da O11 može da popuni KEY/KID polja
    if drm_keys:
        key_objs = build_key_objects(drm_keys)
        # CamelCase varijante
        output["Keys"] = key_objs          # lista objekata {kid, key}
        # lowercase varijante – često upravo ove O11 čita
        output["keys"] = key_objs

    if kid and key:
        # CamelCase
        output["Kid"] = kid                # samo prvi KID
        output["Key"] = key                # samo prvi KEY
        # lowercase
        output["kid"] = kid
        output["key"] = key
    
    return output

# ===============================
# FUNKCIJA ZA AKCIJU "cdm"
# ===============================
def handle_cdm_action():
    """
    CDM akcija za O11 – external widevine (ako ga koristiš ručno):
      - O11 poziva skriptu s: action=cdm&cdm=external&id=XXX&pssh=...
      - Skripta vraća:
          {
            "keys": [ { "kid": "...", "key": "..." }, ... ]
          }
    Panel onda popuni KEY i KID polja iz ovog odgovora.
    """
    mode = cdm_mode or "external"
    logging.info("CDM akcija, mode=%s, id=%s", mode, CHANNEL_ID)

    # podržavamo samo external
    if mode != "external":
        logging.warning("Nepodržan CDM mode: %s", mode)
        return {}

    if not pssh_param:
        logging.error("CDM: pssh param nedostaje")
        return {}

    # Napravi A1 sesiju samo da dobiješ validan device_id za Widevine licencu
    login_username = account if account else DEFAULT_USERNAME
    login_password = password if password else DEFAULT_PASSWORD

    session_data = get_a1_session(login_username, login_password)
    if not session_data:
        logging.error("CDM: neuspješno dobivanje A1 sesije")
        return {}

    device_id = session_data['device_id']
    logging.info("CDM: koristim device_id=%s", device_id)

    drm_keys = do_cdm(pssh_param, device_id)
    if not drm_keys:
        logging.error("CDM: do_cdm nije vratio ključeve")
        return {}

    key_objs = build_key_objects(drm_keys)
    if not key_objs:
        logging.error("CDM: build_key_objects nije vratio validne objekte")
        return {}

    logging.info("CDM: vraćam %d ključeva", len(key_objs))
    # Vraćamo objektni format koji panel očekuje
    return {
        "keys": key_objs,
        "Keys": key_objs
    }

# ===============================
# FUNKCIJA ZA AKCIJU "info"
# ===============================
def handle_info_action():
    return {
        "name": "A1 Hrvatska",
        "logo": "https://play-lh.googleusercontent.com/ymmcWXTqKZbEtmC2tJ6cScy3dFgM4m5-Nm97RstCQpDQ8p-79JEEqlWbTEgqSXk_gTQ",
        "website": "https://go.a1xploretv.hr",
        "type": "iptv",
        "country": "HR"
    }

# ===============================
# FUNKCIJA ZA AKCIJU "validate"
# ===============================
def handle_validate_action():
    login_username = account if account else DEFAULT_USERNAME
    login_password = password if password else DEFAULT_PASSWORD
    
    logging.info("Validacija podataka za korisnika: %s", login_username)
    
    session_data = get_a1_session(login_username, login_password)
    
    if session_data:
        return {"valid": True, "message": "Podaci za prijavu su ispravni"}
    else:
        return {"valid": False, "message": "Neuspješna prijava, provjerite podatke"}

# ===============================
# FUNKCIJA ZA AKCIJU "heartbeat"
# ===============================
def handle_heartbeat_action():
    """
    O11 heartbeat – panel samo provjerava da li je skripta živa.
    Vraćamo jednostavan OK JSON da ne pravi grešku u logovima.
    """
    logging.debug("Primljen heartbeat, vraćam Status=OK")
    return {"Status": "OK"}

# ===============================
# MAIN - RUTIRANJE AKCIJA
# ===============================
def main():
    logging.debug(
        "Start skripte – action=%s, id=%s, worker=%s, bind=%s, account=%s",
        action, CHANNEL_ID, worker, bind, account
    )

    if not action:
        print(json.dumps({"Error": "No action specified"}))
        return
    
    if action == "manifest":
        result = handle_manifest_action()
        print(json.dumps(result))
        
    elif action == "channels":
        result = handle_channels_action()
        print(json.dumps(result))
        
    elif action == "info":
        result = handle_info_action()
        print(json.dumps(result))
        
    elif action == "validate":
        result = handle_validate_action()
        print(json.dumps(result))

    elif action == "cdm":
        # vraća {"keys": [ { "kid": "...", "key": "..." }, ... ]} za KEY/KID polja u O11 panelu
        result = handle_cdm_action()
        print(json.dumps(result))

    elif action == "heartbeat":
        # HEARTBEAT – samo OK, bez error logova
        result = handle_heartbeat_action()
        print(json.dumps(result))
        
    else:
        logging.warning("Unsupported action: %s", action)
        print(json.dumps({"Error": f"Unsupported action: {action}"}))

# ===============================
# POČETAK IZVRŠAVANJA
# ===============================
if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logging.error("Nepredviđena greška: %s", e, exc_info=True)
        print(json.dumps({"Error": f"Script error: {str(e)}"}))
