#!/usr/bin/env python3
"""Ultimate Stream - search & play movies via browser.
Usage: python3 stream.py [port]
Then open http://192.168.1.50:8800 from any device."""
import os, sys, json, html, re, urllib.request, urllib.parse
from http.server import HTTPServer, BaseHTTPRequestHandler

HOST = '0.0.0.0'
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8800

# TorrServer
TS_HOST = '127.0.0.1'
TS_PORT = 8090

# Torrentio API (free, no account needed)
TORRENTIO = 'https://torrentio.stremio.fun'

HEADERS = {'User-Agent': 'UltimateStream/1.0'}

def ts_get(path):
    try:
        r = urllib.request.urlopen(f'http://{TS_HOST}:{TS_PORT}{path}', timeout=10)
        return r.read()
    except: return b''

def ts_post_json(path, data):
    try:
        req = urllib.request.Request(f'http://{TS_HOST}:{TS_PORT}{path}',
            data=json.dumps(data).encode(), headers={'Content-Type':'application/json'})
        return urllib.request.urlopen(req, timeout=10).read()
    except: return b''

def ts_alive():
    try:
        r = urllib.request.urlopen(f'http://{TS_HOST}:{TS_PORT}/echo', timeout=2)
        return bool(r.read())
    except: return False

def search_torrentio(imdb_id, media_type='movie'):
    """Search via Torrentio API by IMDb ID"""
    typ = 'movie' if media_type == 'movie' else 'series'
    url = f'{TORRENTIO}/sort=seeders&quality=*&stream/{typ}/{imdb_id}.json'
    try:
        r = urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS), timeout=15)
        data = json.loads(r.read())
        streams = data.get('streams', [])
        results = []
        for s in streams:
            ih = s.get('infoHash', '')
            title = s.get('title', 'Unknown')
            name = s.get('name', '')
            if not ih: continue
            results.append({'hash': ih, 'title': title, 'name': name,
                          'size': s.get('size', 0), 'seeders': s.get('seeders', 0)})
        return results
    except: return None

def search_tmdb(query):
    """Search TMDB for a movie/TV show to get IMDb ID"""
    q = urllib.parse.quote(query)
    url = f'https://api.tmdb.org/3/search/movie?api_key=7e4d48e68abb9a4f2c86a1cc143cd8b5&query={q}'
    try:
        r = urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS), timeout=10)
        data = json.loads(r.read())
        return [{
            'id': m['id'],
            'title': m.get('title', m.get('name', '')),
            'year': (m.get('release_date') or '')[:4],
            'imdb': '',  # Will need separate IMDB lookup
            'poster': f"https://image.tmdb.org/t/p/w154{m['poster_path']}" if m.get('poster_path') else '',
            'overview': (m.get('overview') or '')[:200],
        } for m in data.get('results', [])[:10]]
    except: return None

def search_tmdb_tv(query):
    q = urllib.parse.quote(query)
    url = f'https://api.tmdb.org/3/search/tv?api_key=7e4d48e68abb9a4f2c86a1cc143cd8b5&query={q}'
    try:
        r = urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS), timeout=10)
        data = json.loads(r.read())
        return [{
            'id': m['id'],
            'title': m.get('name', ''),
            'year': (m.get('first_air_date') or '')[:4],
            'imdb': '',
            'poster': f"https://image.tmdb.org/t/p/w154{m['poster_path']}" if m.get('poster_path') else '',
            'overview': (m.get('overview') or '')[:200],
        } for m in data.get('results', [])[:10]]
    except: return None

def get_imdb_id(tmdb_id, media_type='movie'):
    """Get IMDb ID from TMDB ID"""
    url = f'https://api.themoviedb.org/3/{media_type}/{tmdb_id}/external_ids?api_key=7e4d48e68abb9a4f2c86a1cc143cd8b5'
    try:
        r = urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS), timeout=10)
        data = json.loads(r.read())
        return data.get('imdb_id', '')
    except: return ''

def detect_quality(title):
    t = title.lower()
    if any(x in t for x in ['2160', '4k', 'uhd']): return '4K'
    if '1080' in t: return '1080p'
    if '720' in t: return '720p'
    return 'SD'

def fmt_size(b):
    if not b: return '?'
    g = b / 1073741824
    return f'{g:.2f} GB' if g >= 1 else f'{b/1048576:.0f} MB'

# ── Clean shutdown ──
class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)
        path = parsed.path

        if path == '/player':
            return self.serve_player(params.get('hash', [''])[0],
                                     params.get('title', ['Movie'])[0])
        if path == '/stream':
            return self.proxy_stream(params)
        if path == '/torrents':
            return self.list_torrents()
        if path.startswith('/ts/'):
            return self.proxy_ts(path[4:])

        self.serve_index(params)

    def serve_index(self, params):
        query = params.get('q', [''])[0]
        media_type = params.get('type', ['movie'])[0]
        ts_ok = ts_alive()
        ts_s = '<span style="color:#3fb950">●</span> Live' if ts_ok else '<span style="color:#f85149">●</span> Offline'

        results_html = ''
        search_results = []

        if query:
            if media_type == 'movie':
                search_results = search_tmdb(query)
            else:
                search_results = search_tmdb_tv(query)

        if search_results:
            rows = []
            for r in search_results:
                poster = r.get('poster', '')
                yr = r.get('year', '')
                overview = html.escape(r.get('overview', ''))
                rows.append(f'''<div class="result" onclick="window.location='/player?tmdb={r['id']}&type={media_type}&title={urllib.parse.quote(r['title'])}'">
<img class="poster" src="{poster}" alt="">
<div class="info">
<div class="rtitle">{html.escape(r['title'])} ({yr})</div>
<div class="roverview">{overview}</div>
</div>
<div class="rarrow">→</div>
</div>''')
            results_html = '<div class="results">' + ''.join(rows) + '</div>'
        elif query:
            results_html = '<div class="status">No results found</div>'

        # TorrServer torrents list
        torrents_html = ''
        if ts_ok:
            try:
                tlist = json.loads(ts_post_json('/torrents', {"action":"list"}) or '[]')
                if isinstance(tlist, list) and tlist:
                    items = []
                    for t in tlist[:20]:
                        h = t.get('hash', '')[:16]
                        nm = html.escape(t.get('title', t.get('name', h))[:40])
                        st = t.get('status', '')
                        items.append(f'<div class="ttorrent"><span class="thash">{h}...</span> {nm} <span class="tstatus">{st}</span></div>')
                    torrents_html = '<div class="ttitle">Torrents on Server</div>' + ''.join(items)
            except: pass

        page = f'''<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Ultimate Stream</title>
<style>
*{{box-sizing:border-box;margin:0;padding:0}}
body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#e6edf3;max-width:800px;margin:0 auto;padding:12px}}
h1{{font-size:1.3rem;margin:10px 0;color:#58a6ff;display:flex;align-items:center;gap:8px}}
.sbar{{display:flex;gap:6px;margin-bottom:12px}}
.sbar input{{flex:1;padding:10px 14px;border:1px solid #30363d;border-radius:8px;background:#161b22;color:#e6edf3;font-size:1rem}}
.sbar input:focus{{outline:none;border-color:#58a6ff}}
.sbar button{{padding:10px 18px;background:#238636;border:none;border-radius:8px;color:#fff;font-size:1rem}}
.filt{{display:flex;gap:12px;margin-bottom:12px;font-size:0.85rem;color:#8b949e}}
.filt label{{display:flex;align-items:center;gap:3px;cursor:pointer;padding:4px 10px;border-radius:12px;border:1px solid #30363d}}
.filt label.active{{border-color:#58a6ff;color:#58a6ff}}
.result{{display:flex;gap:10px;padding:10px;border-bottom:1px solid #21262d;cursor:pointer;border-radius:8px}}
.result:hover{{background:#161b22}}
.poster{{width:50px;height:75px;border-radius:4px;object-fit:cover;background:#21262d;flex-shrink:0}}
.info{{flex:1;min-width:0}}
.rtitle{{font-weight:600;font-size:0.95rem;margin-bottom:4px}}
.roverview{{font-size:0.8rem;color:#8b949e;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}}
.rarrow{{display:flex;align-items:center;color:#30363d;font-size:1.2rem}}
.status{{padding:30px;text-align:center;color:#8b949e}}
.ttitle{{margin-top:16px;padding:8px 0;font-size:0.8rem;color:#8b949e;border-top:1px solid #21262d}}
.ttorrent{{padding:4px 0;font-size:0.8rem;display:flex;gap:8px}}
.thash{{font-family:monospace;color:#58a6ff}}
.tstatus{{color:#8b949e;margin-left:auto}}
.footer{{margin-top:12px;padding:10px 0;font-size:0.75rem;color:#484f58;display:flex;justify-content:space-between}}
</style>
</head><body>
<h1><span style="font-size:1.5rem">🎬</span> Ultimate Stream {ts_s}</h1>
<form class="sbar" method="get" action="/">
  <input type="text" name="q" placeholder="Search movies..." value="{html.escape(query)}" autofocus>
  <button>Search</button>
</form>
<div class="filt">
  <label class="active" onclick="document.forms[0].elements['type'].value='movie';document.forms[0].submit()">Movies</label>
  <label onclick="document.forms[0].elements['type'].value='tv';document.forms[0].submit()">TV Shows</label>
  <input type="hidden" name="type" value="{media_type}">
</div>
{results_html}
{torrents_html}
<div class="footer">
<span>TorrServer: {ts_s}</span>
<span><a href="/torrents" style="color:#58a6ff;text-decoration:none">Torrents</a></span>
</div>
</body></html>'''
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(page.encode())

    def serve_player(self, hash_val, title):
        if not hash_val and not title:
            self.send_response(302); self.send_header('Location', '/'); self.end_headers(); return

        # If we have a hash, add to TorrServer and get stream URL
        stream_url = ''
        ts_ok = ts_alive()
        if hash_val and ts_ok:
            # Add magnet to TorrServer
            magnet = f'magnet:?xt=urn:btih:{hash_val}'
            try:
                ts_post_json('/torrents', {"action":"add","link":magnet,"save_to_db":True})
            except: pass
            stream_url = f'/ts/play?hash={hash_val}&file_id=0'

        page = f'''<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>{html.escape(title)} - Ultimate Stream</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{background:#000;color:#e6edf3;font-family:sans-serif}}
.player-wrap{{width:100%;max-width:100vw;background:#000}}
video{{width:100%;max-height:100vh;display:block;background:#000}}
.controls{{padding:12px;max-width:800px;margin:0 auto}}
h2{{font-size:1rem;margin-bottom:8px;color:#58a6ff}}
.back{{color:#8b949e;text-decoration:none;font-size:0.85rem;display:inline-block;margin-bottom:8px}}
.status{{color:#8b949e;font-size:0.85rem;padding:12px;text-align:center}}
.status .spinner{{display:inline-block;width:20px;height:20px;border:2px solid #30363d;border-top-color:#58a6ff;border-radius:50%;animation:spin .8s linear infinite;margin-right:8px;vertical-align:middle}}
@keyframes spin{{to{{transform:rotate(360deg)}}}}
</style>
</head><body>
<div class="player-wrap">
{"<video id='v' controls autoplay><source src='"+stream_url+"' type='video/mp4'></video>" if stream_url else '<div class="status"><div class="spinner"></div>Loading torrent...<br><small>Stream will appear shortly</small></div>'}
</div>
<div class="controls">
<a class="back" href="/">← Back to search</a>
<h2>{html.escape(title[:60])}</h2>
<div class="status" style="text-align:left;padding:0">
{'' if stream_url else '<span id="s">Connecting to TorrServer... <span class="spinner"></span></span>'}
</div>
</div>
<script>
{hack}
</script>
</body></html>'''
        hack = '''
let h = new URL(location).searchParams.get('hash');
if(h && !document.getElementById('v')) {
    let check = setInterval(() => {
        fetch('/ts/play?hash='+h+'&file_id=0', {method:'HEAD'}).then(r => {
            if(r.ok || r.status==206) {
                clearInterval(check);
                document.querySelector('.player-wrap').innerHTML =
                    "<video id='v' controls autoplay><source src='/ts/play?hash="+h+"&file_id=0' type='video/mp4'></video>";
                document.getElementById('s').textContent = 'Stream ready!';
            }
        }).catch(()=>{});
    }, 2000);
}'''
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(page.encode())

    def proxy_stream(self, params):
        """Proxy stream from TorrServer through us (avoids CORS issues)"""
        hash_val = params.get('hash', [''])[0]
        file_id = params.get('file_id', ['0'])[0]
        if not hash_val:
            self.send_response(400); self.end_headers(); return
        ts_url = f'http://{TS_HOST}:{TS_PORT}/play?hash={hash_val}&file_id={file_id}'
        try:
            ts_resp = urllib.request.urlopen(urllib.request.Request(ts_url, headers=HEADERS), timeout=30)
            self.send_response(200)
            self.send_header('Content-Type', ts_resp.headers.get('Content-Type', 'video/mp4'))
            self.send_header('Content-Length', ts_resp.headers.get('Content-Length', ''))
            self.send_header('Accept-Ranges', 'bytes')
            self.end_headers()
            while True:
                chunk = ts_resp.read(65536)
                if not chunk: break
                self.wfile.write(chunk)
        except Exception as e:
            self.send_response(502)
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(f'Stream error: {e}'.encode())

    def proxy_ts(self, path):
        """Generic proxy to TorrServer API"""
        try:
            ts_resp = urllib.request.urlopen(
                urllib.request.Request(f'http://{TS_HOST}:{TS_PORT}/{path}', headers=HEADERS), timeout=30)
            data = ts_resp.read()
            ct = ts_resp.headers.get('Content-Type', 'application/octet-stream')
            self.send_response(200)
            self.send_header('Content-Type', ct)
            self.send_header('Content-Length', str(len(data)))
            self.send_header('Accept-Ranges', 'bytes')
            self.send_header('Access-Control-Allow-Origin', '*')
            self.end_headers()
            self.wfile.write(data)
        except Exception as e:
            self.send_response(502)
            self.end_headers()
            self.wfile.write(str(e).encode())

    def list_torrents(self):
        ts_ok = ts_alive()
        page = f'''<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Torrents - Ultimate Stream</title>
<style>*{{margin:0;padding:0;box-sizing:border-box}}
body{{font-family:sans-serif;background:#0d1117;color:#e6edf3;padding:12px;max-width:600px;margin:0 auto}}
h1{{font-size:1.2rem;margin:10px 0;color:#58a6ff}}
a{{color:#58a6ff;text-decoration:none}}
.titem{{padding:8px;border-bottom:1px solid #21262d;font-size:0.85rem}}
.thash{{font-family:monospace;color:#8b949e}}
.ts{{color:#8b949e;margin-left:8px;font-size:0.75rem}}
.back{{margin-top:12px;display:block;font-size:0.85rem}}</style></head><body>
<h1>Torrents on Server</h1>'''
        if ts_ok:
            try:
                tlist = json.loads(ts_post_json('/torrents', {"action":"list"}) or '[]')
                if isinstance(tlist, list):
                    for t in tlist[:50]:
                        h = t.get('hash','')[:16]
                        nm = html.escape(t.get('title',t.get('name','Unknown'))[:50])
                        st = t.get('status','')
                        page += f'<div class="titem"><a href="/player?hash={t.get("hash","")}&title={urllib.parse.quote(t.get("title",""))}"><span class="thash">{h}</span> {nm} <span class="ts">{st}</span></a></div>'
                else:
                    page += '<div class="titem">No torrents</div>'
            except:
                page += '<div class="titem">Error listing torrents</div>'
        else:
            page += '<div class="titem">TorrServer offline</div>'
        page += '<a class="back" href="/">← Back</a></body></html>'
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.end_headers()
        self.wfile.write(page.encode())

    def log_message(self, fmt, *a):
        print(f"[{self.client_address[0]}] {fmt%a}")

if __name__ == '__main__':
    server = HTTPServer((HOST, PORT), Handler)
    ip = urllib.request.urlopen('https://api.ipify.org', timeout=5).read().decode()
    print(f"\n  🎬 Ultimate Stream running!")
    print(f"  📱 http://{ip}:{PORT}")
    print(f"  🌐 http://192.168.1.50:{PORT}")
    print(f"  🖥️  http://localhost:{PORT}")
    print(f"\n  Press Ctrl+C to stop\n")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()
        print("\nStopped.")
