#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from datetime import datetime, timezone
from urllib.parse import urlparse
import html
import json
import os
import re
import shutil
import tempfile
import cgi

HOST = '127.0.0.1'
PORT = 18950
BASE_DIR = Path('/home/sebas/outputs/inbox/ballbox-dropzone')
BASE_DIR.mkdir(parents=True, exist_ok=True)
MAX_BYTES = 512 * 1024 * 1024


def safe_name(name: str) -> str:
    name = os.path.basename(name).strip().replace('\x00', '')
    name = re.sub(r'[^A-Za-z0-9._ -]+', '-', name)
    name = re.sub(r'\s+', '-', name)
    name = name.strip('.-')
    return name or 'upload.bin'


def stamp_name(name: str) -> str:
    p = Path(name)
    ts = datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')
    stem = safe_name(p.stem)
    suffix = ''.join(p.suffixes) or ''
    return f'{ts}-{stem}{suffix}'


def file_rows() -> str:
    rows = []
    for p in sorted(BASE_DIR.iterdir(), key=lambda x: x.stat().st_mtime, reverse=True):
        if not p.is_file():
            continue
        size_mb = p.stat().st_size / (1024 * 1024)
        mtime = datetime.fromtimestamp(p.stat().st_mtime).strftime('%Y-%m-%d %H:%M')
        href = f'/files/outputs/inbox/ballbox-dropzone/{p.name}'
        rows.append(f'<tr><td><a href="{html.escape(href)}">{html.escape(p.name)}</a></td><td>{size_mb:.1f} MB</td><td>{mtime}</td></tr>')
    return '\n'.join(rows) or '<tr><td colspan="3">Vacío</td></tr>'


def page(message: str = '') -> bytes:
    msg = f'<p class="msg">{html.escape(message)}</p>' if message else ''
    body = f'''<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Ballbox Dropzone</title>
  <style>
    body {{ font-family: system-ui, sans-serif; margin: 0; background: #0f172a; color: #e2e8f0; }}
    main {{ max-width: 760px; margin: 0 auto; padding: 24px; }}
    .card {{ background: #111827; border: 1px solid #334155; border-radius: 16px; padding: 20px; margin-bottom: 20px; }}
    h1,h2 {{ margin-top: 0; }}
    .drop {{ border: 2px dashed #38bdf8; border-radius: 16px; padding: 24px; text-align: center; background: #0b1220; }}
    input[type=file] {{ width: 100%; margin: 12px 0; }}
    button {{ background: #22c55e; color: #08110b; border: 0; border-radius: 10px; padding: 12px 16px; font-weight: 700; width: 100%; }}
    .msg {{ background: #132238; border: 1px solid #38bdf8; padding: 12px; border-radius: 10px; white-space: pre-wrap; }}
    table {{ width: 100%; border-collapse: collapse; }}
    td,th {{ text-align: left; padding: 8px; border-top: 1px solid #334155; font-size: 14px; }}
    a {{ color: #7dd3fc; }}
    .hint {{ color: #94a3b8; font-size: 14px; }}
  </style>
</head>
<body>
  <main>
    <div class="card">
      <h1>Ballbox Dropzone</h1>
      <p>Subí acá los archivos de la máquina.</p>
      <p class="hint">Acepta uno o varios archivos juntos. Quedan guardados en /home/sebas/outputs/inbox/ballbox-dropzone/</p>
      {msg}
      <form class="drop" action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" accept=".zip,.rar,.7z,.tar,.gz,.mp3,.doc,.docx,.txt,*/*" multiple required>
        <button type="submit">Subir archivo(s)</button>
      </form>
    </div>
    <div class="card">
      <h2>Archivos recibidos</h2>
      <table>
        <thead><tr><th>Archivo</th><th>Tamaño</th><th>Fecha</th></tr></thead>
        <tbody>{file_rows()}</tbody>
      </table>
    </div>
  </main>
</body>
</html>'''
    return body.encode('utf-8')


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urlparse(self.path)
        if parsed.path in ('/', ''):
            self.respond_html(200, page())
            return
        if parsed.path == '/health':
            self.respond_json(200, {'ok': True, 'dir': str(BASE_DIR)})
            return
        self.respond_text(404, 'not found')

    def do_POST(self):
        parsed = urlparse(self.path)
        if parsed.path != '/upload':
            self.respond_text(404, 'not found')
            return
        ctype, pdict = cgi.parse_header(self.headers.get('content-type', ''))
        if ctype != 'multipart/form-data':
            self.respond_html(400, page('Pedido inválido.'))
            return
        form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'}, keep_blank_values=True)
        items = form['file'] if 'file' in form else None
        if items is None:
            self.respond_html(400, page('Falta archivo.'))
            return
        if not isinstance(items, list):
            items = [items]

        uploaded = []
        try:
            for item in items:
                if getattr(item, 'file', None) is None or not getattr(item, 'filename', None):
                    continue
                name = stamp_name(item.filename)
                tmp_fd, tmp_path = tempfile.mkstemp(dir=str(BASE_DIR), prefix='.upload-')
                total = 0
                try:
                    with os.fdopen(tmp_fd, 'wb') as out:
                        while True:
                            chunk = item.file.read(1024 * 1024)
                            if not chunk:
                                break
                            total += len(chunk)
                            if total > MAX_BYTES:
                                raise ValueError(f'Archivo demasiado grande: {item.filename}')
                            out.write(chunk)
                    final_path = BASE_DIR / name
                    os.replace(tmp_path, final_path)
                    uploaded.append(name)
                except Exception:
                    try:
                        os.unlink(tmp_path)
                    except FileNotFoundError:
                        pass
                    raise

            if not uploaded:
                self.respond_html(400, page('Falta archivo.'))
                return

            summary = 'Subidos:\n- ' + '\n- '.join(uploaded)
            self.respond_html(200, page(summary))
        except Exception as e:
            self.respond_html(400, page(str(e)))

    def log_message(self, fmt, *args):
        return

    def respond_html(self, code, payload: bytes):
        self.send_response(code)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.send_header('Content-Length', str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)

    def respond_text(self, code, text):
        payload = text.encode('utf-8')
        self.send_response(code)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.send_header('Content-Length', str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)

    def respond_json(self, code, obj):
        payload = json.dumps(obj).encode('utf-8')
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)


if __name__ == '__main__':
    httpd = ThreadingHTTPServer((HOST, PORT), Handler)
    httpd.serve_forever()
