import requests
import json
import time
import random
import logging
import os
import threading
from urllib.parse import urlparse, parse_qs, quote

# Configuration
PORTAL_URL = os.environ.get("STALKER_PORTAL_URL", "http://line.best4kprovider.com/c/")
MAC_ADDRESS = os.environ.get("STALKER_MAC_ADDRESS", "00:1B:79:47:5A:2D")
PROXY_PORT = int(os.environ.get("STALKER_PROXY_PORT", 5000))
DEVICE_ID = os.environ.get("STALKER_DEVICE_ID", "32C98AE5F5FC7FC69785F817A8C87E368B6127F5102C0F77A540A17D84C8D5F8")
SERIAL_NUMBER = os.environ.get("STALKER_SERIAL_NUMBER", "AA5EC7F1A340B")

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class StalkerClient:
    def __init__(self, portal_url, mac):
        self.portal_url = portal_url.rstrip('/')
        self.mac = mac
        self.token = None
        self.token_expiry = 0
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG250',
            'X-User-Agent': 'Model: MAG250; Link: WiFi',
            'Cookie': f'mac={mac}; stb_lang=en; timezone=Europe/Kiev',
            'Accept': 'application/json, text/javascript, */*; q=0.01',
            'Referer': self.portal_url + '/',
        })
        # Link cache: {cmd: {'url': real_url, 'expires': timestamp}}
        self.link_cache = {}
        self.lock = threading.Lock()

    def _get_api_url(self, action, type_="stb", **kwargs):
        url = f"{self.portal_url}/server/load.php?type={type_}&action={action}"
        for k, v in kwargs.items():
            url += f"&{k}={quote(str(v))}"
        return url

    def authenticate(self):
        if self.token and time.time() < self.token_expiry:
            return self.token

        logger.info("Authenticating...")
        try:
            url = self._get_api_url("handshake")
            resp = self.session.get(url)
            data = resp.json()
            token = data['js']['token']

            self.session.headers.update({'Authorization': f'Bearer {token}'})
            url = self._get_api_url("get_profile",
                                    hd=1,
                                    auth_second_step=0,
                                    num_banks=1,
                                    sn=SERIAL_NUMBER,
                                    device_id=DEVICE_ID,
                                    device_id2=DEVICE_ID,
                                    stb_type="MAG250",
                                    ver="ImageDescription: 0.2.18-r14-pub-250; ImageDate: Fri Jan 15 15:20:44 EET 2016; PORTAL version: 5.6.1; API Version: JS API version: 328; STB API version: 134;",
                                    not_valid_token=0)

            resp = self.session.get(url)
            if resp.status_code == 200:
                self.token = token
                self.token_expiry = time.time() + 300
                logger.info("Authentication successful.")
                return token
            else:
                logger.error(f"Authentication failed: {resp.text}")
                return None
        except Exception as e:
            logger.error(f"Authentication error: {e}")
            return None

    def get_genres(self):
        try:
            with self.lock:
                self.authenticate()
                url = self._get_api_url("get_genres", type_="itv")
                resp = self.session.get(url)
                data = resp.json()
                if 'js' in data:
                    return data['js']
                return []
        except Exception as e:
            logger.error(f"get_genres exception: {e}")
            return []

    def get_all_channels(self):
        try:
            with self.lock:
                self.authenticate()
                url = self._get_api_url("get_all_channels", type_="itv")
                resp = self.session.get(url)
                data = resp.json()
                if 'js' in data and 'data' in data['js']:
                    return data['js']['data']
                return []
        except Exception as e:
            logger.error(f"get_all_channels exception: {e}")
            return []

    def create_link(self, cmd):
        with self.lock:
            # Check cache first
            if cmd in self.link_cache:
                entry = self.link_cache[cmd]
                # Reuse link if it's less than 20 seconds old (prevent spamming portal on retry)
                if time.time() < entry['expires']:
                    logger.info(f"Using cached link for {cmd}")
                    return entry['url']

            self.authenticate()
            # Try to stop previous stream? Stalker usually handles this on new create_link.

            url = self._get_api_url("create_link", type_="itv", cmd=cmd, forced_storage="undefined", disable_ad=0, download=0, JsHttpRequest="1-xml")
            resp = self.session.get(url)
            try:
                data = resp.json()
                if 'js' in data and 'cmd' in data['js']:
                    link = data['js']['cmd']
                    if 'http' in link:
                        link = link.split(' ')[-1] if ' ' in link else link

                    # Cache the result for a short duration (e.g. 15s)
                    self.link_cache[cmd] = {'url': link, 'expires': time.time() + 15}
                    return link
            except:
                pass
            return None

from flask import Flask, Response, redirect, request, stream_with_context

app = Flask(__name__)
client = StalkerClient(PORTAL_URL, MAC_ADDRESS)

@app.route('/')
def index():
    host = request.host
    return f"""
    <h1>Stalker Restream Proxy</h1>
    <p>Status: Running</p>
    <p>Portal: {PORTAL_URL}</p>
    <p>MAC: {MAC_ADDRESS}</p>
    <hr>
    <h3>Links:</h3>
    <ul>
        <li><a href="http://{host}/playlist.m3u">Download Playlist (M3U)</a></li>
    </ul>
    """

@app.route('/playlist.m3u')
def playlist():
    host = request.host
    try:
        genres = client.get_genres()
        channels = client.get_all_channels()
        genre_map = {g['id']: g['title'] for g in genres}
        m3u = ["#EXTM3U"]
        for ch in channels:
            name = ch.get('name', 'Unknown')
            cmd = ch.get('cmd', '')
            logo = ch.get('logo', '')
            gid = ch.get('tv_genre_id', '')
            gname = genre_map.get(gid, 'Other')
            proxy_link = f"http://{host}/stream/{quote(cmd)}"
            m3u.append(f'#EXTINF:-1 tvg-name="{name}" tvg-logo="{logo}" group-title="{gname}",{name}')
            m3u.append(proxy_link)
        return Response("\n".join(m3u), mimetype="audio/x-mpegurl")
    except Exception as e:
        logger.error(f"Error generating playlist: {e}")
        return Response(f"Error generating playlist: {e}", status=500)

@app.route('/stream/<path:cmd>')
def stream(cmd):
    try:
        real_link = client.create_link(cmd)
        if not real_link:
             return Response("Could not generate link", status=404)

        # Fix stream ID if missing
        import re
        stream_id_match = re.search(r'[?&]stream=([^&]+)', cmd)
        if stream_id_match and 'stream=&' in real_link:
            stream_id = stream_id_match.group(1)
            real_link = real_link.replace('stream=&', f'stream={stream_id}&')
            print(f"Fixed link: {real_link}", flush=True)

        print(f"Proxying {cmd} -> {real_link}", flush=True)

        headers = client.session.headers.copy()
        headers['Range'] = 'bytes=0-'
        if 'Authorization' in headers:
             del headers['Authorization']

        # If we get 444, maybe we should retry with re-auth?
        req = client.session.get(real_link, stream=True, allow_redirects=True, headers=headers)

        if req.status_code == 444 or req.status_code == 403:
            logger.warning("Got 444/403 Ban. Attempting re-authentication...")
            with client.lock:
                client.token = None # Force re-auth
                client.authenticate()
            # Retry once
            req = client.session.get(real_link, stream=True, allow_redirects=True, headers=headers)

        excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(name, value) for (name, value) in req.headers.items()
                   if name.lower() not in excluded_headers]

        def generate():
            for chunk in req.iter_content(chunk_size=1024*1024):
                yield chunk

        return Response(stream_with_context(generate()), headers=headers, status=req.status_code)

    except Exception as e:
        logger.error(f"Error creating link: {e}")
        return Response("Internal Error", status=500)

if __name__ == '__main__':
    logger.info(f"Starting Stalker Proxy (CACHING MODE) for {PORTAL_URL} [{MAC_ADDRESS}] on port {PROXY_PORT}")
    app.run(host='0.0.0.0', port=PROXY_PORT, threaded=True)
