// apiv2-bridge — a SIDE-SERVICE that lets external apps post to the legacy PHP
// `/apiv2/*` API without a browser cookie.
//
// This is an ISOLATED, ADDITIVE router. It does not touch, wrap, or sit in the
// path of any existing flow:
//   - The browser frontend keeps hitting Caddy's `/apiv2` -> PHP directly with
//     its own session cookie. Unchanged.
//   - This router is mounted at `/api/apiv2-bridge` (a deliberately distinct
//     prefix that cannot be confused with the real `/apiv2`). It inherits the
//     same user-JWT auth every `/api/*` route already enforces — the same token
//     the product-import skill already sends.
//
// Flow:  external app --(user JWT)--> /api/apiv2-bridge/<subpath>
//          -> acotBackendSession swaps in a server-held PHP cookie
//          -> POST backend.acherryontop.com/apiv2/<subpath>
//          -> PHP JSON response relayed back verbatim.
//
// It is a constrained passthrough: only the known product/category/PO write
// surfaces are allowed, so a valid token can't be used to proxy arbitrary
// backend paths.

import express from 'express';
import { apiv2Request, checkSession, isHtmlResponse } from '../utils/acotBackendSession.js';
import { logger } from '../../shared/logging/logger.js';

const router = express.Router();

// Allowed `/apiv2/<...>` path prefixes. Covers product edits/imports, category
// creation, and purchase-order creation — the surfaces the skills need.
const ALLOWED_PREFIXES = ['product/', 'prod_cat/', 'po/'];

function isAllowed(subpath) {
  return ALLOWED_PREFIXES.some((prefix) => subpath === prefix.slice(0, -1) || subpath.startsWith(prefix));
}

// Health/credential check: forces a fresh login and reports the result so an
// operator can validate ACOT_BACKEND_USERID/PIN without sending a real payload.
router.get('/_health', async (_req, res) => {
  try {
    const status = await checkSession();
    res.json({ ok: status.authenticated, ...status });
  } catch (err) {
    logger.error({ err }, 'apiv2-bridge health check failed');
    res.status(502).json({ ok: false, error: err.message });
  }
});

// Reconstruct an outbound body + content-type from the already-parsed Express
// request. The global json/urlencoded parsers have consumed the raw stream, so
// we re-serialize from req.body to match what the caller sent.
function serializeBody(req) {
  const incoming = (req.headers['content-type'] || '').toLowerCase();

  if (incoming.includes('application/json')) {
    return { contentType: 'application/json; charset=utf-8', body: JSON.stringify(req.body ?? {}) };
  }

  if (incoming.includes('application/x-www-form-urlencoded')) {
    // The real payloads use flat string fields (e.g. `products`/`items` are
    // already JSON strings), which round-trip cleanly through URLSearchParams.
    const params = new URLSearchParams();
    for (const [key, value] of Object.entries(req.body ?? {})) {
      params.append(key, typeof value === 'string' ? value : JSON.stringify(value));
    }
    return { contentType: 'application/x-www-form-urlencoded; charset=utf-8', body: params.toString() };
  }

  // No/own content-type: forward as-is (string or undefined).
  return { contentType: incoming || undefined, body: req.body };
}

// Single passthrough handler for every allowed apiv2 subpath. The wildcard
// captures everything after the mount point (e.g. `product/setup_new`).
router.all('/*', async (req, res, next) => {
  // `req.params[0]` is the wildcard capture; strip any leading slash.
  const subpath = String(req.params[0] || '').replace(/^\/+/, '');

  if (!subpath || subpath.startsWith('_')) return next();

  if (!isAllowed(subpath)) {
    return res.status(403).json({
      error: 'Path not allowed through the apiv2 bridge',
      allowed: ALLOWED_PREFIXES,
    });
  }

  // Preserve the original query string (e.g. `?use_test_data_source=1`).
  const qIndex = req.originalUrl.indexOf('?');
  const query = qIndex === -1 ? '' : req.originalUrl.slice(qIndex + 1);

  const { contentType, body } = serializeBody(req);

  try {
    const upstream = await apiv2Request({
      method: req.method,
      path: `/apiv2/${subpath}`,
      query,
      contentType,
      body,
    });

    // If PHP still served the HTML login page (re-login inside apiv2Request
    // couldn't recover the session), don't relay it as a misleading 200 — the
    // caller would have to sniff HTML to notice. Surface it as a clear 502.
    if (isHtmlResponse(upstream.data)) {
      logger.error({ subpath, status: upstream.status }, 'apiv2-bridge got HTML (PHP session unavailable)');
      return res.status(502).json({
        error: 'Backend session unavailable',
        message: 'The PHP backend returned its login page — the service account session could not be established. Check /api/apiv2-bridge/_health.',
      });
    }

    logger.info(
      { user: req.user?.username, method: req.method, subpath, status: upstream.status },
      'apiv2-bridge relayed request'
    );

    res.status(upstream.status).type(upstream.contentType).send(upstream.data);
  } catch (err) {
    logger.error({ err, subpath }, 'apiv2-bridge relay failed');
    res.status(502).json({ error: 'Upstream backend request failed', message: err.message });
  }
});

export default router;
