#!/usr/bin/env python3
"""
BloomWealth Board — local server
Serves dashboard.html and a simple tasks REST API backed by tasks.json.

Usage:
  python3 ~/board/server.py
  python3 ~/board/server.py --port 7788

Claude updates ~/board/tasks.json directly; the dashboard reads/writes via this server.
"""
import json
import os
import sys
import uuid
import urllib.parse
from datetime import date
from http.server import HTTPServer, SimpleHTTPRequestHandler

BOARD_DIR   = os.path.dirname(os.path.abspath(__file__))
TASKS_FILE  = os.path.join(BOARD_DIR, 'tasks.json')
UPLOAD_DIR  = os.path.join(BOARD_DIR, 'uploads')
PORT        = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[1] == '--port' else 7788

MAX_UPLOAD  = 25 * 1024 * 1024  # 25 MB per file
ALLOWED_EXT = {
    # images
    '.png', '.jpg', '.jpeg', '.gif', '.webp', '.heic', '.bmp', '.svg',
    # documents
    '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.csv', '.txt', '.rtf',
    '.ppt', '.pptx', '.pages', '.numbers', '.key', '.md',
}

os.makedirs(UPLOAD_DIR, exist_ok=True)


class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=BOARD_DIR, **kwargs)

    def log_message(self, fmt, *args):
        pass  # suppress access logs

    def _cors(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')

    def do_OPTIONS(self):
        self.send_response(204)
        self._cors()
        self.end_headers()

    def do_GET(self):
        if self.path in ('/api/tasks', '/api/tasks/'):
            data = b'[]'
            if os.path.exists(TASKS_FILE):
                with open(TASKS_FILE, 'rb') as f:
                    data = f.read()
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self._cors()
            self.end_headers()
            self.wfile.write(data)

        elif self.path in ('/api/health', '/api/health/'):
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self._cors()
            self.end_headers()
            self.wfile.write(b'{"ok":true}')

        else:
            super().do_GET()

    def do_POST(self):
        if self.path in ('/api/tasks', '/api/tasks/'):
            length = int(self.headers.get('Content-Length', 0))
            body   = self.rfile.read(length)
            try:
                tasks = json.loads(body)
                assert isinstance(tasks, list)
            except Exception:
                self.send_response(400)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(b'{"error":"invalid JSON"}')
                return

            with open(TASKS_FILE, 'w', encoding='utf-8') as f:
                json.dump(tasks, f, indent=2, ensure_ascii=False)

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self._cors()
            self.end_headers()
            self.wfile.write(b'{"ok":true}')

        elif self.path in ('/api/upload', '/api/upload/'):
            self._handle_upload()

        else:
            self.send_response(405)
            self.end_headers()

    def _json(self, code, obj):
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self._cors()
        self.end_headers()
        self.wfile.write(json.dumps(obj).encode('utf-8'))

    def _handle_upload(self):
        length = int(self.headers.get('Content-Length', 0))
        if length <= 0:
            return self._json(400, {'error': 'empty body'})
        if length > MAX_UPLOAD:
            return self._json(413, {'error': 'file too large (max 25 MB)'})

        raw_name = urllib.parse.unquote(self.headers.get('X-Filename', 'file'))
        raw_name = os.path.basename(raw_name)  # strip any path
        ctype    = self.headers.get('Content-Type', 'application/octet-stream')
        ext      = os.path.splitext(raw_name)[1].lower()
        if ext not in ALLOWED_EXT:
            return self._json(415, {'error': f'file type {ext or "?"} not allowed'})

        # Read exactly Content-Length bytes
        remaining = length
        chunks = []
        while remaining > 0:
            buf = self.rfile.read(min(remaining, 1 << 16))
            if not buf:
                break
            chunks.append(buf)
            remaining -= len(buf)
        data = b''.join(chunks)

        stored = uuid.uuid4().hex + ext
        try:
            with open(os.path.join(UPLOAD_DIR, stored), 'wb') as f:
                f.write(data)
        except OSError as e:
            return self._json(500, {'error': f'save failed: {e}'})

        self._json(200, {
            'ok': True,
            'name': raw_name,
            'url': '/uploads/' + stored,
            'type': ctype,
            'size': len(data),
            'uploaded': date.today().isoformat(),
        })


if __name__ == '__main__':
    server = HTTPServer(('localhost', PORT), Handler)
    print(f'\n  BloomWealth Board')
    print(f'  → http://localhost:{PORT}/dashboard.html')
    print(f'  Tasks: {TASKS_FILE}')
    print(f'  Press Ctrl+C to stop.\n')
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print('  Stopped.')
