'use strict';

/**
 * Validation for the user-supplied host / target fields on a Host entry.
 *
 * Neither field may carry a scheme (http://), a path ("/"), a port or ":" of any
 * kind, or whitespace.
 *
 *   host (incoming)  — an IPv4 address or a hostname pattern whose dot-separated
 *                      labels may be normal DNS labels or wildcard fragments:
 *                        "*"  matches exactly one subdomain fragment
 *                        "**" matches any number of fragments
 *                      e.g. "*.example.com", "**.mysite.com", "payments.**", and
 *                      a bare "**" as a global catch-all. (Matched by
 *                      Host.lookUp in models/host.js.)
 *   ip (target)      — a concrete destination: an IPv4 address, a single
 *                      unqualified label (e.g. "sso-manager" — a Docker
 *                      Compose service name, or any other host resolvable
 *                      via /etc/hosts or a search domain), or a dotted
 *                      hostname with an alphabetic TLD. No wildcards.
 *
 * Pure (no I/O) so it can be unit tested and reused. Enforced at the route layer
 * (routes/host.js) so internally-created entries (wildcard children, on-demand
 * cache) are unaffected.
 */

// A single DNS label.
const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
// A strict hostname: either one bare label (Docker service names, /etc/hosts
// entries, search-domain-relative names) or dotted labels + alphabetic TLD.
const HOSTNAME = /^(?=.{1,253}$)(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/i;
// Scheme, path, port, or whitespace — anything that means it isn't a bare host.
const FORBIDDEN = /[\s/:]/;

function isValidIPv4(value){
	if(typeof value !== 'string') return false;
	let parts = value.split('.');
	if(parts.length !== 4) return false;
	// Each octet 0-255, no leading zeros (0 itself is fine).
	return parts.every(p => /^(0|[1-9]\d{0,2})$/.test(p) && Number(p) <= 255);
}

// A strict, concrete hostname (used for the downstream target). No wildcards.
function isValidHostname(value){
	return typeof value === 'string' && HOSTNAME.test(value);
}

// An incoming-host pattern: dot-separated labels, each a normal label or a
// wildcard fragment ("*" / "**"). A bare "**" is the global catch-all.
function isValidHostPattern(value){
	if(typeof value !== 'string' || value.length === 0 || value.length > 253) return false;
	return value.split('.').every(l => l === '*' || l === '**' || LABEL.test(l));
}

// The incoming `host` field: IPv4 or a wildcard host pattern, no forbidden chars.
function isValidHostField(value){
	if(typeof value !== 'string' || value.length === 0) return false;
	if(FORBIDDEN.test(value)) return false;
	return isValidIPv4(value) || isValidHostPattern(value);
}

// The `ip` (target) field: IPv4 or a strict hostname, no forbidden chars.
function isValidTargetField(value){
	if(typeof value !== 'string' || value.length === 0) return false;
	if(FORBIDDEN.test(value)) return false;
	return isValidIPv4(value) || isValidHostname(value);
}

const NO_CHARS = 'no protocol, "/", or ":".';

/**
 * Collect {key, message} errors for whichever of host / ip are present on the
 * body. Absent fields are skipped (presence/length is handled by the model), so
 * this works for both create (both present) and partial update.
 */
function collectHostFieldErrors(body){
	let errors = [];
	body = body || {};

	if(body.host !== undefined && body.host !== null && body.host !== ''){
		if(!isValidHostField(body.host)){
			errors.push({key: 'host', message: `Host must be a hostname, IP, or wildcard pattern (*, **) — ${NO_CHARS}`});
		}
	}
	if(body.ip !== undefined && body.ip !== null && body.ip !== ''){
		if(!isValidTargetField(body.ip)){
			errors.push({key: 'ip', message: `Target must be a valid hostname or IP address — ${NO_CHARS}`});
		}
	}
	return errors;
}

module.exports = {
	isValidIPv4,
	isValidHostname,
	isValidHostPattern,
	isValidHostField,
	isValidTargetField,
	collectHostFieldErrors,
};
