#!/usr/bin/env python3
"""
Surgical Review Server with Auto-Upload Support
- Serves static HTML and video files with Range Request support
- Receives uploaded audio and JSON data from the review platform
- Saves uploads to ./uploads/ folder organized by participant and case
"""

import os
import sys
import json
import time
from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib.parse import parse_qs
import cgi

UPLOAD_DIR = 'uploads'

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True

class ReviewServerHandler(SimpleHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'

    def do_POST(self):
        """Handle file uploads from the review platform"""
        if self.path == '/upload':
            try:
                content_type = self.headers.get('Content-Type', '')
                
                if 'multipart/form-data' in content_type:
                    # Parse multipart form data
                    form = cgi.FieldStorage(
                        fp=self.rfile,
                        headers=self.headers,
                        environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
                    )
                    
                    filename = form.getfirst('filename', 'unknown')
                    participant = form.getfirst('participant', 'unknown')
                    case_id = form.getfirst('caseId', 'unknown')
                    
                    # Create organized folder structure
                    save_dir = os.path.join(UPLOAD_DIR, f'{participant}_{case_id}')
                    os.makedirs(save_dir, exist_ok=True)
                    
                    # Save the file
                    file_item = form['file']
                    if file_item.file:
                        filepath = os.path.join(save_dir, filename)
                        with open(filepath, 'wb') as f:
                            while True:
                                chunk = file_item.file.read(8192)
                                if not chunk:
                                    break
                                f.write(chunk)
                        
                        size = os.path.getsize(filepath)
                        print(f"[UPLOAD] {filepath} ({size} bytes)")
                        
                        self.send_response(200)
                        self.send_header('Content-Type', 'application/json')
                        self.send_header('Access-Control-Allow-Origin', '*')
                        self.end_headers()
                        self.wfile.write(json.dumps({
                            'status': 'ok',
                            'filename': filename,
                            'size': size,
                            'path': filepath
                        }).encode())
                        return
                
                elif 'application/json' in content_type:
                    # Handle JSON upload directly
                    content_length = int(self.headers.get('Content-Length', 0))
                    body = self.rfile.read(content_length)
                    data = json.loads(body)
                    
                    participant = data.get('participant', 'unknown')
                    case_id = data.get('caseId', 'unknown')
                    filename = data.get('filename', f'data_{int(time.time())}.json')
                    
                    save_dir = os.path.join(UPLOAD_DIR, f'{participant}_{case_id}')
                    os.makedirs(save_dir, exist_ok=True)
                    
                    filepath = os.path.join(save_dir, filename)
                    with open(filepath, 'w') as f:
                        json.dump(data.get('data', data), f, indent=2)
                    
                    print(f"[UPLOAD] {filepath} (JSON)")
                    
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.send_header('Access-Control-Allow-Origin', '*')
                    self.end_headers()
                    self.wfile.write(json.dumps({'status': 'ok', 'filename': filename}).encode())
                    return
                    
                self.send_error(400, "Bad request")
                
            except Exception as e:
                print(f"[ERROR] Upload failed: {e}")
                self.send_response(500)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()
                self.wfile.write(json.dumps({'status': 'error', 'message': str(e)}).encode())
        else:
            self.send_error(404, "Not found")

    def do_OPTIONS(self):
        """Handle CORS preflight requests"""
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()

    def do_GET(self):
        """Serve static files with Range Request support for video streaming"""
        try:
            f = self.send_head()
            if f:
                try:
                    self.copyfile(f, self.wfile)
                finally:
                    f.close()
        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError, OSError):
            pass

    def send_head(self):
        path = self.translate_path(self.path.split('?')[0])  # Strip query params

        if os.path.isdir(path):
            return super().send_head()

        if not os.path.exists(path):
            self.send_error(404, "File not found")
            return None

        ctype = self.guess_type(path)
        file_size = os.path.getsize(path)
        range_header = self.headers.get('Range')

        if range_header:
            try:
                range_spec = range_header.replace('bytes=', '')
                parts = range_spec.split('-')
                start = int(parts[0]) if parts[0] else 0
                end = int(parts[1]) if len(parts) > 1 and parts[1] else file_size - 1

                if start >= file_size:
                    self.send_error(416, "Range Not Satisfiable")
                    return None

                end = min(end, file_size - 1)
                length = end - start + 1

                self.send_response(206)
                self.send_header("Content-Type", ctype)
                self.send_header("Content-Length", str(length))
                self.send_header("Content-Range", f"bytes {start}-{end}/{file_size}")
                self.send_header("Accept-Ranges", "bytes")
                self.send_header("Cache-Control", "public, max-age=3600")
                self.send_header("Connection", "keep-alive")
                self.end_headers()

                f = open(path, 'rb')
                f.seek(start)
                return _LimitedFile(f, length)
            except:
                pass

        try:
            f = open(path, 'rb')
        except:
            self.send_error(404, "File not found")
            return None

        self.send_response(200)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(file_size))
        self.send_header("Accept-Ranges", "bytes")
        self.send_header("Cache-Control", "public, max-age=3600")
        self.send_header("Connection", "keep-alive")
        self.end_headers()
        return f

    def log_message(self, format, *args):
        # Only log uploads and errors, not every static file request
        msg = format % args
        if 'POST' in msg or '404' in msg or '500' in msg:
            print(f"[{self.log_date_time_string()}] {msg}")


class _LimitedFile:
    def __init__(self, f, length):
        self.f = f
        self.remaining = length

    def read(self, size=65536):
        if self.remaining <= 0:
            return b''
        size = min(size, self.remaining)
        data = self.f.read(size)
        self.remaining -= len(data)
        return data

    def close(self):
        self.f.close()


if __name__ == '__main__':
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
    os.makedirs(UPLOAD_DIR, exist_ok=True)
    
    server = ThreadedHTTPServer(('', port), ReviewServerHandler)
    print(f"=" * 50)
    print(f"  Surgical Review Server")
    print(f"  Port: {port}")
    print(f"  Uploads: ./{UPLOAD_DIR}/")
    print(f"  Videos:  ./videos/")
    print(f"=" * 50)
    print(f"Press Ctrl+C to stop\n")
    
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nStopped")
