#!/usr/bin/env python3
"""
SwingTrader local HTTP server.

Serves all files in this directory AND handles position writes via POST /api/positions.

Usage:
    python3 server.py           # port 8282 (default)
    python3 server.py 9000      # custom port

Then open: http://localhost:8282/swingtrader_dashboard_v2.1.html
"""

import json
import os
import sys
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from datetime import date

BASE           = os.path.dirname(os.path.abspath(__file__))
POSITIONS_FILE = os.path.join(BASE, 'positions.json')


def apply_position_change(payload):
    with open(POSITIONS_FILE, 'r') as f:
        data = json.load(f)

    action = (payload.get('action') or '').lower().strip()
    ticker = (payload.get('ticker') or '').strip().upper()
    today  = date.today().isoformat()

    if not ticker:
        raise ValueError('ticker is required')

    if action == 'add':
        entry_date      = payload.get('entry_date') or today
        entry_price_cad = float(payload['entry_price_cad'])
        shares          = int(payload['shares'])
        if entry_price_cad <= 0:
            raise ValueError('entry_price_cad must be > 0')
        if shares < 1:
            raise ValueError('shares must be >= 1')
        new_pos = {
            'ticker':         ticker,
            'company':        payload.get('company', ''),
            'entry_date':     entry_date,
            'entry_price_cad': entry_price_cad,
            'shares':         shares,
            'stop_loss_cad':  None,
            'target_1_cad':   None,
            'target_2_cad':   None,
            'target_3_cad':   None,
            'target_1_hit':   False,
            'target_2_hit':   False,
            'setup_type':     '',
            'days_held':      0,
            'notes':          payload.get('notes', ''),
        }
        data.setdefault('open_positions', []).append(new_pos)

    elif action == 'modify':
        open_pos = data.get('open_positions', [])
        pos = next((p for p in open_pos if (p.get('ticker') or '').upper() == ticker), None)
        if pos is None:
            raise ValueError(f'{ticker} not found in open_positions')

        mod_date  = payload.get('modification_date') or today
        mod_price = float(payload.get('modification_price_cad', pos['entry_price_cad']))
        delta     = int(payload.get('shares', 0))

        if delta == 0:
            raise ValueError('share change (shares delta) cannot be zero')
        if mod_price <= 0:
            raise ValueError('modification_price_cad must be > 0')

        new_shares = pos['shares'] + delta
        if new_shares <= 0:
            raise ValueError(
                f'Result would be {new_shares} shares. '
                'Use Remove action to close a position entirely.'
            )

        if delta > 0:
            # Buying more: recalculate weighted average entry
            avg = (pos['shares'] * pos['entry_price_cad'] + delta * mod_price) / new_shares
            pos['entry_price_cad'] = round(avg, 4)

        pos['shares'] = new_shares
        note_suffix = f'Modified {mod_date}: {delta:+d}sh @ ${mod_price:.2f}'
        pos['notes'] = ((pos.get('notes') or '') + ' | ' + note_suffix).lstrip(' |')

    elif action == 'remove':
        open_pos = data.get('open_positions', [])
        idx = next(
            (i for i, p in enumerate(open_pos) if (p.get('ticker') or '').upper() == ticker),
            None
        )
        if idx is None:
            raise ValueError(f'{ticker} not found in open_positions')

        exit_date  = payload.get('exit_date') or today
        exit_price = float(payload.get('exit_price_cad', 0))
        if exit_price <= 0:
            raise ValueError('exit_price_cad must be > 0')

        pos = open_pos.pop(idx)
        closed = {
            **pos,
            'close_date':   exit_date,
            'close_reason': f'Exited at ${exit_price:.2f} CAD',
        }
        data.setdefault('closed_positions', []).append(closed)

    else:
        raise ValueError(f'Unknown action "{action}". Expected: add, modify, remove')

    data['last_updated'] = today

    with open(POSITIONS_FILE, 'w') as f:
        json.dump(data, f, indent=2)

    return {'ok': True, 'last_updated': today}


class SwingTraderHandler(SimpleHTTPRequestHandler):
    def do_OPTIONS(self):
        self._cors_headers(200)
        self.end_headers()

    def do_POST(self):
        if self.path == '/api/positions':
            length = int(self.headers.get('Content-Length', 0))
            body   = self.rfile.read(length)
            try:
                payload = json.loads(body)
                result  = apply_position_change(payload)
                self._json_response(200, result)
            except (KeyError, TypeError, ValueError) as e:
                self._json_response(400, {'error': str(e)})
            except Exception as e:
                self._json_response(500, {'error': str(e)})
        else:
            self.send_response(404)
            self.end_headers()

    def _cors_headers(self, code):
        self.send_response(code)
        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 _json_response(self, code, obj):
        body = json.dumps(obj).encode()
        self._cors_headers(code)
        self.send_header('Content-Type',   'application/json')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        # Suppress noisy GET logs for JSON files; keep POST logs
        if args and str(args[0]).startswith('POST'):
            super().log_message(fmt, *args)


if __name__ == '__main__':
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8282
    os.chdir(BASE)
    server = ThreadingHTTPServer(('0.0.0.0', port), SwingTraderHandler)
    print(f'SwingTrader server → http://localhost:{port}/swingtrader_dashboard_v2.1.html')
    print('Press Ctrl+C to stop.')
    server.serve_forever()
