// Checking and normalising a URL the user typed for a service they already run.
//
// ── What must never be assumed ──
//
// That the instance is on this machine, on its project's usual port, or reachable without a scheme. It
// may be on another host, behind a reverse proxy on 443 with a path, on a tailnet address, or on an
// arbitrary port because 8096 was already taken. Every one of those is an ordinary self-hosted setup,
// and each is a case where guessing produces a connection that fails later with no clue why.
//
// So the rule here is narrow: reject only what CANNOT work, normalise what is merely untidy, and accept
// everything else without opinion. In particular no check that the host is local, that the port matches
// the service's default, or that the scheme is https — a tailnet HTTP service is completely normal.

export type UrlCheck = { ok: true; url: string } | { ok: false; error: string };

/**
 * Validate and normalise. Returns the form to store, or why it cannot be stored.
 *
 * Normalising matters more than it looks: a trailing slash turns `${url}/api/x` into a double
 * separator, which some servers accept and others 404 — the kind of difference that shows up on one
 * user's machine and not another's.
 */
export function checkServiceUrl(raw: string): UrlCheck {
  const trimmed = raw.trim();
  if (!trimmed) return { ok: false, error: 'A URL is required.' };

  // The commonest mistake, and worth naming precisely rather than as "invalid URL": people type the
  // host alone because that is what they type into a browser.
  if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)) {
    return { ok: false, error: `Include the scheme — "https://${trimmed}" or "http://${trimmed}".` };
  }

  let parsed: URL;
  try {
    parsed = new URL(trimmed);
  } catch {
    return { ok: false, error: 'That is not a valid URL.' };
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    return { ok: false, error: `Only http and https are supported, not "${parsed.protocol.replace(':', '')}".` };
  }
  if (!parsed.hostname) return { ok: false, error: 'That URL has no host.' };

  // Credentials in the URL are dropped rather than stored: they would end up in a database column meant
  // for a location, outside the encrypted `secret`, and in any log line that ever prints the URL.
  if (parsed.username || parsed.password) {
    return {
      ok: false,
      error: 'Put the username and password in their own fields rather than in the URL.',
    };
  }

  // Trailing slash off, query and fragment dropped — neither means anything for a service base, and a
  // stored `?token=…` would be a credential hiding in the wrong column.
  const path = parsed.pathname.replace(/\/+$/, '');
  return { ok: true, url: `${parsed.protocol}//${parsed.host}${path}` };
}
