'use strict';

const http = require('http');
const fs = require('fs');
const path = require('path');
const { URL } = require('url');

/**
 * Local dashboard server.
 *
 * BINDS TO 127.0.0.1 ONLY. This is not a configurable host. The dashboard is a
 * single-user local surface on the customer's own machine; exposing it on
 * 0.0.0.0 would put a pause/resume control for a trading bot on the local
 * network. Remote access is the relay's job — outbound-only and opt-in.
 *
 * Uses Node's built-in http rather than Express deliberately: the bot has no
 * native dependencies and no web framework, which keeps a compiled single-file
 * .exe viable (TECH_SPEC_V5 §9 Option B).
 *
 * ─────────────────────────────────────────────────────────────────────────────
 * ENDPOINT CONTRACT — the dashboard frontend builds against EXACTLY these.
 *
 *   GET  /api/status            full state snapshot
 *   GET  /api/dashboard-config  { licenseServerBaseUrl } — may be null
 *   POST /api/pause             → { status }
 *   POST /api/resume            → { status }
 *
 * The frontend spec forbids adding backend endpoints to satisfy the UI. If the
 * dashboard needs something new, it gets added here first, deliberately.
 *
 * Control surface is pause/resume ONLY (FE-C7). No "execute trade now", no
 * threshold editing, and no way to disable pre-execution validation — NFR-18
 * makes that check unconditional, including from the UI.
 * ─────────────────────────────────────────────────────────────────────────────
 */

const HOST = '127.0.0.1';

const CONTENT_TYPES = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
  '.ico': 'image/x-icon',
};

function createDashboardServer({ state, controls, env = process.env, publicDir }) {
  const root = publicDir || path.join(__dirname, '..', '..', 'public');

  function sendJson(res, status, body) {
    const payload = JSON.stringify(body);
    res.writeHead(status, {
      'Content-Type': 'application/json; charset=utf-8',
      'Content-Length': Buffer.byteLength(payload),
      // Local-only surface; no caching of live state.
      'Cache-Control': 'no-store',
    });
    res.end(payload);
  }

  function serveStatic(req, res, pathname) {
    const rel = pathname === '/' ? 'dashboard.html' : pathname.replace(/^\/+/, '');
    const target = path.join(root, rel);

    // Path traversal guard — never serve outside public/.
    if (!target.startsWith(path.resolve(root) + path.sep) && target !== path.resolve(root, rel)) {
      res.writeHead(403).end('Forbidden');
      return;
    }
    const resolved = path.resolve(target);
    if (!resolved.startsWith(path.resolve(root))) {
      res.writeHead(403).end('Forbidden');
      return;
    }

    fs.readFile(resolved, (err, data) => {
      if (err) {
        res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not found');
        return;
      }
      res.writeHead(200, {
        'Content-Type': CONTENT_TYPES[path.extname(resolved)] || 'application/octet-stream',
      });
      res.end(data);
    });
  }

  const server = http.createServer((req, res) => {
    let parsed;
    try {
      parsed = new URL(req.url, `http://${HOST}`);
    } catch {
      res.writeHead(400).end('Bad request');
      return;
    }
    const pathname = parsed.pathname;

    if (req.method === 'GET' && pathname === '/api/status') {
      return sendJson(res, 200, state.snapshot());
    }

    /**
     * Exposes LICENSE_SERVER_BASE_URL to the frontend so the Buy and Builder
     * Pro tabs know where to call. Returns null rather than omitting the field
     * when unset, so the UI can render a clear "not configured" state naming
     * the variable instead of failing silently (FE-B1).
     */
    if (req.method === 'GET' && pathname === '/api/dashboard-config') {
      const url = env.LICENSE_SERVER_BASE_URL
        ? String(env.LICENSE_SERVER_BASE_URL).trim().replace(/\/+$/, '')
        : null;
      return sendJson(res, 200, {
        licenseServerBaseUrl: url || null,
        variableName: 'LICENSE_SERVER_BASE_URL',
      });
    }

    if (req.method === 'POST' && (pathname === '/api/pause' || pathname === '/api/resume')) {
      const wantPause = pathname === '/api/pause';
      try {
        if (wantPause) controls.pause();
        else controls.resume();
      } catch (err) {
        return sendJson(res, 500, { error: err.message });
      }
      // Respond with REAL state so the UI re-renders from the response rather
      // than assuming its optimistic guess was right (FE-S4).
      return sendJson(res, 200, {
        status: state.snapshot().bot.status,
        lastUpdated: state.snapshot().lastUpdated,
      });
    }

    if (req.method === 'GET') {
      return serveStatic(req, res, pathname);
    }

    sendJson(res, 404, { error: 'Not found' });
  });

  return {
    server,
    listen(port) {
      return new Promise((resolve, reject) => {
        server.once('error', reject);
        // 127.0.0.1 — never 0.0.0.0, never configurable.
        server.listen(port, HOST, () => resolve(server.address().port));
      });
    },
    close() {
      return new Promise((resolve) => server.close(resolve));
    },
    host: HOST,
  };
}

module.exports = { createDashboardServer, HOST };
