#!/usr/bin/env python3
"""
Local dev server + CORS proxy for the Knicks dashboard.

Two jobs:
  1. Serves files from the current directory over HTTP.
     Fixes the `file://` origin problem — browsers can't fetch() to
     https:// from file:// pages even when the upstream allows it.
  2. Proxies API calls at  /proxy?url=<url-encoded-target>
     Fixes any CORS header issues from ESPN, NBA, or rss2json.

Usage (from the folder containing knicks-dashboard.html):

    python3 proxy.py

Then open:  http://localhost:8765/knicks-dashboard.html

The dashboard auto-detects localhost and routes through /proxy
automatically — no HTML edit needed.

Requirements: Python 3.7+  (stdlib only, no pip install)
"""

import http.server
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request

PORT = int(os.environ.get('PORT', '8765'))
TIMEOUT = 15  # seconds for upstream requests

# Allowlist of hosts this proxy will forward to. Prevents it from becoming
# an open proxy if the port is ever exposed beyond localhost.
ALLOWED_HOSTS = {
    'site.api.espn.com',
    'site.web.api.espn.com',
    'sports.core.api.espn.com',
    'cdn.espn.com',
    'cdn.nba.com',
    'stats.nba.com',
    'api.rss2json.com',
    'www.reddit.com',
    'old.reddit.com',
    'reddit.com',
}


class Handler(http.server.SimpleHTTPRequestHandler):

    # ── Routing ────────────────────────────────────────────────────────
    def do_GET(self):
        path = urllib.parse.urlparse(self.path).path
        if path == '/proxy':
            self.handle_proxy()
        elif path == '/health':
            self.send_json(200, {'ok': True, 'hosts': sorted(ALLOWED_HOSTS)})
        else:
            # Serve static files from cwd
            super().do_GET()

    def do_OPTIONS(self):
        # CORS preflight — only matters if someone calls this proxy from
        # a different origin than where it's serving the HTML.
        self.send_response(204)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', '*')
        self.send_header('Access-Control-Max-Age', '86400')
        self.end_headers()

    # ── Proxy handler ──────────────────────────────────────────────────
    def handle_proxy(self):
        qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
        target_url = (qs.get('url') or [''])[0]

        if not target_url:
            return self.send_json(400, {'error': 'Missing ?url= parameter'})

        try:
            target = urllib.parse.urlparse(target_url)
        except Exception as e:
            return self.send_json(400, {'error': f'Invalid URL: {e}'})

        if target.scheme not in ('http', 'https'):
            return self.send_json(400, {'error': f'Unsupported scheme: {target.scheme}'})

        if target.netloc not in ALLOWED_HOSTS:
            return self.send_json(403, {
                'error': f'Host not in allowlist: {target.netloc}',
                'allowed_hosts': sorted(ALLOWED_HOSTS),
                'hint': 'Edit ALLOWED_HOSTS in proxy.py to add more upstreams.',
            })

        # Browser-ish headers. Reddit and stats.nba.com both dislike
        # generic bot User-Agents, so we send a realistic Chrome string.
        req = urllib.request.Request(target_url, headers={
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'en-US,en;q=0.9',
            'Referer': f'https://{target.netloc}/',
        })

        try:
            with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
                body = resp.read()
                ct = resp.headers.get('Content-Type', 'application/json')
            self.send_response(200)
            self.send_header('Content-Type', ct)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Cache-Control', 'no-store')
            self.send_header('X-Proxied-From', target.netloc)
            self.send_header('Content-Length', str(len(body)))
            self.end_headers()
            self.wfile.write(body)
        except urllib.error.HTTPError as e:
            self.send_json(e.code, {
                'error': f'Upstream HTTP {e.code}',
                'reason': str(e.reason),
                'upstream': target.netloc,
            })
        except urllib.error.URLError as e:
            self.send_json(502, {
                'error': 'Upstream unreachable',
                'reason': str(e.reason),
                'upstream': target.netloc,
            })
        except Exception as e:
            self.send_json(500, {'error': f'Proxy internal error: {e}'})

    # ── Helpers ────────────────────────────────────────────────────────
    def send_json(self, code, payload):
        body = json.dumps(payload, indent=2).encode('utf-8')
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        # One-line compact log
        sys.stderr.write(f'[{self.log_date_time_string()}] {fmt % args}\n')


def main():
    addr = ('127.0.0.1', PORT)
    try:
        httpd = http.server.ThreadingHTTPServer(addr, Handler)
    except OSError as e:
        if getattr(e, 'errno', None) in (48, 98) or 'in use' in str(e).lower():
            print(f'\n  ERROR: Port {PORT} is already in use.', file=sys.stderr)
            print(f'  Try:  PORT=8766 python3 proxy.py\n', file=sys.stderr)
            sys.exit(1)
        raise

    bar = '━' * 62
    print(bar)
    print('  Knicks Dashboard — local dev server')
    print(bar)
    print(f'  Dashboard:  http://localhost:{PORT}/knicks-dashboard.html')
    print(f'  Proxy:      http://localhost:{PORT}/proxy?url=<encoded>')
    print(f'  Health:     http://localhost:{PORT}/health')
    print(f'  Serving:    {os.getcwd()}')
    print(f'  Allowlist:  {len(ALLOWED_HOSTS)} upstream hosts')
    print()
    print('  Ctrl+C to stop.')
    print(bar)

    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print('\n  Stopped.')


if __name__ == '__main__':
    main()
