#!/usr/bin/env python3
"""
MP3 upload server for Japanese study audio.
Run this on your Mac in the directory you want to serve.

Usage:
  python3 mp3_server.py

Then:
  - Files are served at http://127.0.0.1:8000/<path>
  - Upload MP3s via: curl -X PUT -H "X-Auth-Token: YOUR_TOKEN" --data-binary @file.mp3 http://127.0.0.1:8000/upload/file.mp3
  - Uploaded files go to ./audio/ and are served at http://127.0.0.1:8000/audio/file.mp3

If you're using Tailscale Funnel, the public URLs will be:
  https://philips-mac-studio.tail93c452.ts.net/audio/file.mp3
"""

import os
import re
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, unquote

# Configuration
PORT = 8000
AUDIO_DIR = "audio"
# Auth token: set via MP3_AUTH_TOKEN env var (recommended), or edit below.
# The server refuses to start with the default value.
AUTH_TOKEN = os.environ.get("MP3_AUTH_TOKEN", "change-me-to-a-random-string")

# Validate auth token is changed
if AUTH_TOKEN == "change-me-to-a-random-string":
    raise SystemExit(
        "ERROR: Set the MP3_AUTH_TOKEN environment variable before running.\n"
        "Example:  MP3_AUTH_TOKEN=$(openssl rand -hex 24) python3 mp3_server.py"
    )

os.makedirs(AUDIO_DIR, exist_ok=True)

# Filename must be safe: alphanumeric, dash, underscore, ending in .mp3
FILENAME_RE = re.compile(r'^[a-zA-Z0-9_-]+\.mp3$')

class MP3UploadHandler(SimpleHTTPRequestHandler):
    def do_PUT(self):
        """Handle MP3 file uploads at /upload/<filename>.mp3"""
        parsed = urlparse(self.path)
        path = unquote(parsed.path)
        
        if not path.startswith("/upload/"):
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"Not found. Use PUT /upload/<filename>.mp3")
            return
        
        filename = os.path.basename(path[8:])  # strip "/upload/"
        
        # Validate filename
        if not FILENAME_RE.match(filename):
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Invalid filename. Use alphanumeric, dash, underscore, ending in .mp3")
            return
        
        # Check auth token
        auth = self.headers.get("X-Auth-Token", "")
        if auth != AUTH_TOKEN:
            self.send_response(401)
            self.end_headers()
            self.wfile.write(b"Unauthorized: invalid X-Auth-Token")
            return
        
        # Read the MP3 data
        try:
            length = int(self.headers.get("Content-Length", 0))
        except ValueError:
            length = 0
        
        if length == 0 or length > 10 * 1024 * 1024:  # 10MB max
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Invalid content length (must be 1 byte to 10MB)")
            return
        
        data = self.rfile.read(length)
        
        # Basic MP3 validation: check for ID3 header or MP3 frame sync
        if not (data.startswith(b"ID3") or (len(data) > 2 and data[0] == 0xFF and (data[1] & 0xE0) == 0xE0)):
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Not a valid MP3 file")
            return
        
        # Save the file
        filepath = os.path.join(AUDIO_DIR, filename)
        with open(filepath, "wb") as f:
            f.write(data)
        
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(f"Saved {filename} ({len(data)} bytes)".encode())
        print(f"Uploaded: {filename} ({len(data)} bytes)")
    
    def do_GET(self):
        """Serve files (inherited from SimpleHTTPRequestHandler)."""
        # Add CORS header for audio playback
        super().do_GET()
    
    def end_headers(self):
        # Allow cross-origin requests (for Feed widgets)
        self.send_header("Access-Control-Allow-Origin", "*")
        super().end_headers()
    
    def log_message(self, format, *args):
        # Quieter logging
        print(f"{self.address_string()} - {format % args}")

if __name__ == "__main__":
    # Change to the script's directory (serve files from here)
    script_dir = os.path.dirname(os.path.abspath(__file__))
    os.chdir(script_dir)
    
    # Bind all interfaces: reachable via Tailscale (tailnet-only, not public
    # internet) for uploads, and via localhost for Tailscale Serve/Funnel.
    server = HTTPServer(("0.0.0.0", PORT), MP3UploadHandler)
    print(f"MP3 server running on port {PORT} (all interfaces)")
    print(f"Audio files served from ./{AUDIO_DIR}/")
    print(f"Upload via: PUT /upload/<filename>.mp3 with X-Auth-Token header")
    print(f"Press Ctrl+C to stop")
    print()
    
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
        server.shutdown()
