<?php

declare(strict_types=1);

namespace FormConnector\Core\Validation;

/**
 * UK Phone Number Validator
 * 
 * Lightweight GB-only phone validation using patterns from Google's libphonenumber.
 * Validates and formats UK phone numbers before sending to CareHQ.
 * 
 * Pattern source: https://github.com/google/libphonenumber
 * GB general: [1-357-9]\d{9}|[18]\d{8}|8\d{6}
 * GB mobile: 7(?:[1-4]\d\d|5(?:0[0-8]|[13-9]\d|2[0-35-9])|7(?:0[1-9]|[1-7]\d|8[02-9]|9[0-689])|8(?:[014-9]\d|[23][0-8])|9(?:[024-9]\d|1[02-9]|3[0-689]))\d{6}
 * GB fixed: patterns for 1xxx, 2xxx area codes
 */
class PhoneValidator {

	/**
	 * UK country code
	 */
	private const UK_COUNTRY_CODE = '44';

	/**
	 * Regex pattern for valid UK mobile numbers (national format without leading 0)
	 * From libphonenumber GB mobile pattern
	 */
	private const MOBILE_PATTERN = '/^7(?:[1-4]\d\d|5(?:0[0-8]|[13-9]\d|2[0-35-9])|7(?:0[1-9]|[1-7]\d|8[02-9]|9[0-689])|8(?:[014-9]\d|[23][0-8])|9(?:[024-9]\d|1[02-9]|3[0-689]))\d{6}$/';

	/**
	 * Regex pattern for valid UK fixed-line numbers (national format without leading 0)
	 * From libphonenumber GB fixed-line pattern (simplified for common area codes)
	 */
	private const FIXED_LINE_PATTERN = '/^(?:1(?:1\d|[2-69]\d{2}|[78]\d{2})|2[0-9]\d{2}|3[0-3]\d{2})\d{5,6}$/';

	/**
	 * Regex pattern for UK toll-free numbers
	 */
	private const TOLL_FREE_PATTERN = '/^80[08]\d{7}|800\d{6}$/';

	/**
	 * Regex pattern for UK premium rate numbers
	 */
	private const PREMIUM_PATTERN = '/^(?:8(?:4[2-5]|7[0-3])|9(?:[01]\d|8[2-49]))\d{7}$/';

	/**
	 * Regex pattern for UK personal numbers (070)
	 */
	private const PERSONAL_PATTERN = '/^70\d{8}$/';

	/**
	 * Regex pattern for UK VoIP numbers (056)
	 */
	private const VOIP_PATTERN = '/^56\d{8}$/';

	/**
	 * Combined pattern for all valid UK numbers (national format without leading 0)
	 * Lengths: 9-10 digits (after removing country code/leading 0)
	 */
	private const UK_GENERAL_PATTERN = '/^[1-357-9]\d{8,9}$/';

	/**
	 * Validate a phone number for GB
	 *
	 * @param string $phone Phone number to validate
	 * @return bool True if valid GB phone number
	 */
	public function is_valid(string $phone): bool {
		$normalized = $this->normalize($phone);
		
		if ($normalized === null) {
			return false;
		}

		return $this->is_valid_uk_number($normalized);
	}

	/**
	 * Format a phone number to E.164 international format
	 *
	 * @param string $phone Phone number to format
	 * @return string|null Formatted phone number (+44...) or null if invalid
	 */
	public function format(string $phone): ?string {
		$normalized = $this->normalize($phone);
		
		if ($normalized === null || !$this->is_valid_uk_number($normalized)) {
			return null;
		}

		return '+' . self::UK_COUNTRY_CODE . $normalized;
	}

	/**
	 * Format a phone number to national format
	 *
	 * @param string $phone Phone number to format
	 * @return string|null Formatted phone number (0...) or null if invalid
	 */
	public function format_national(string $phone): ?string {
		$normalized = $this->normalize($phone);
		
		if ($normalized === null || !$this->is_valid_uk_number($normalized)) {
			return null;
		}

		// Add leading 0 and format with space after area code
		$national = '0' . $normalized;
		
		// Format based on number type
		if (str_starts_with($normalized, '7')) {
			// Mobile: 07xxx xxxxxx
			return substr($national, 0, 5) . ' ' . substr($national, 5);
		} elseif (str_starts_with($normalized, '2')) {
			// London/major cities: 020 xxxx xxxx
			return substr($national, 0, 3) . ' ' . substr($national, 3, 4) . ' ' . substr($national, 7);
		} else {
			// Standard geographic: 01234 567890
			return substr($national, 0, 5) . ' ' . substr($national, 5);
		}
	}

	/**
	 * Validate and format a phone number, returning result with details
	 *
	 * @param string $phone Phone number to validate
	 * @return array{valid: bool, formatted: string|null, error: string|null}
	 */
	public function validate_and_format(string $phone): array {
		if (empty(trim($phone))) {
			return [
				'valid' => false,
				'formatted' => null,
				'error' => 'Phone number is empty',
			];
		}

		$normalized = $this->normalize($phone);
		
		if ($normalized === null) {
			return [
				'valid' => false,
				'formatted' => null,
				'error' => 'Could not parse phone number',
			];
		}

		if (!$this->is_valid_uk_number($normalized)) {
			return [
				'valid' => false,
				'formatted' => null,
				'error' => 'Invalid UK phone number format',
			];
		}

		return [
			'valid' => true,
			'formatted' => '+' . self::UK_COUNTRY_CODE . $normalized,
			'error' => null,
		];
	}

	/**
	 * Normalize a phone number to digits only without country code
	 * 
	 * @param string $phone Raw phone input
	 * @return string|null Normalized number (digits after country code) or null if unparseable
	 */
	private function normalize(string $phone): ?string {
		// Remove all non-digit characters except leading +
		$cleaned = preg_replace('/[^\d+]/', '', trim($phone));
		
		if (empty($cleaned)) {
			return null;
		}

		// Handle different input formats
		if (str_starts_with($cleaned, '+44')) {
			// International format: +44 7xxx...
			$number = substr($cleaned, 3);
		} elseif (str_starts_with($cleaned, '0044')) {
			// International format: 0044 7xxx...
			$number = substr($cleaned, 4);
		} elseif (str_starts_with($cleaned, '44') && strlen($cleaned) > 10) {
			// International without +: 44 7xxx... (check length to avoid false positives)
			$number = substr($cleaned, 2);
		} elseif (str_starts_with($cleaned, '0')) {
			// National format: 07xxx... or 01234...
			$number = substr($cleaned, 1);
		} else {
			// Assume it's already without prefix
			$number = $cleaned;
		}

		// Remove any remaining leading zeros
		$number = ltrim($number, '0');
		
		if (empty($number)) {
			return null;
		}

		return $number;
	}

	/**
	 * Check if normalized number is a valid UK number using libphonenumber patterns
	 * 
	 * @param string $number Normalized number (digits after 44, no leading 0)
	 * @return bool True if valid UK number
	 */
	private function is_valid_uk_number(string $number): bool {
		// Must be digits only
		if (!ctype_digit($number)) {
			return false;
		}

		// UK numbers (after country code) are 9-10 digits
		$length = strlen($number);
		if ($length < 9 || $length > 10) {
			return false;
		}

		// Check against specific type patterns (from libphonenumber)
		// Mobile: 7xxxxxxxxx
		if (preg_match(self::MOBILE_PATTERN, $number)) {
			return true;
		}

		// Fixed-line: 1xxxxxxxxx or 2xxxxxxxxx
		if (preg_match(self::FIXED_LINE_PATTERN, $number)) {
			return true;
		}

		// Toll-free: 80xxxxxxxxx
		if (preg_match(self::TOLL_FREE_PATTERN, $number)) {
			return true;
		}

		// Premium rate: 84x, 87x, 9xx
		if (preg_match(self::PREMIUM_PATTERN, $number)) {
			return true;
		}

		// Personal numbers: 70xxxxxxxx
		if (preg_match(self::PERSONAL_PATTERN, $number)) {
			return true;
		}

		// VoIP: 56xxxxxxxx
		if (preg_match(self::VOIP_PATTERN, $number)) {
			return true;
		}

		// Fallback: check general UK pattern for other valid numbers (non-geo 3xx, etc.)
		if (str_starts_with($number, '3') && $length === 10) {
			return true; // Non-geographic numbers like 03xx
		}

		return false;
	}
}
