<?php

declare( strict_types=1 );

namespace Brightfern\Validation;

use Brightfern\Invoice\Model\Address;
use Brightfern\Support\Http\HttpClient;

/**
 * VAT + legal-name validation via VIES (the EU VAT Information Exchange System).
 * Confirms the buyer's VAT number is real and active and returns the official
 * registered name/address so the caller can compare it to what's on the order.
 *
 * Resilience is the whole point: VIES is frequently slow or briefly down, and a
 * member state's service can be individually unavailable. A failure to reach
 * VIES yields status UNKNOWN, never INVALID — we never block a legitimate
 * invoice because a government endpoint hiccuped.
 *
 * Uses the VIES REST endpoint (JSON); the SOAP endpoint is an alternative when
 * ext-soap is present but REST keeps the dependency surface small.
 */
final class PartyValidator {

	private const REST_BASE = 'https://ec.europa.eu/taxation_customs/vies/rest-api/ms';

	public function __construct( private HttpClient $http ) {}

	/**
	 * @param string $vatNumber e.g. "BE0123456789" (country prefix + number).
	 */
	public function validateVat( string $vatNumber ): PartyValidationResult {
		$clean   = strtoupper( preg_replace( '/[^A-Za-z0-9]/', '', $vatNumber ) ?? '' );
		$country = substr( $clean, 0, 2 );
		$number  = substr( $clean, 2 );

		// EU_VAT_PREFIXES, NOT EU_COUNTRIES: a VAT number's own two-letter prefix
		// is a different code list from a country's ISO code (Greece is the
		// divergent case — EL, not its ISO code GR). Using the country list here
		// meant a fabricated Greek VAT number could never reach VIES at all, so
		// it could never trigger the blocking VIES-INVALID result every other
		// EU country's forged VAT number correctly triggers.
		if ( '' === $number || ! in_array( $country, Address::EU_VAT_PREFIXES, true ) ) {
			return new PartyValidationResult(
				PartyValidationResult::UNKNOWN,
				$vatNumber,
				'',
				'',
				'VAT number is empty or not an EU member-state number; VIES not applicable.'
			);
		}

		$url      = sprintf( '%s/%s/vat/%s', self::REST_BASE, rawurlencode( $country ), rawurlencode( $number ) );
		$response = $this->http->get( $url, array( 'Accept' => 'application/json' ) );

		if ( $response->transportFailed() || ! $response->ok() ) {
			return new PartyValidationResult(
				PartyValidationResult::UNKNOWN,
				$vatNumber,
				'',
				'',
				$response->error ?: ( 'VIES unavailable (HTTP ' . $response->status . ').' )
			);
		}

		$data = $response->json();
		if ( null === $data ) {
			return new PartyValidationResult( PartyValidationResult::UNKNOWN, $vatNumber, '', '', 'Unexpected VIES response.' );
		}

		// The REST API has used both "valid" and "isValid" across versions.
		$valid = $data['valid'] ?? $data['isValid'] ?? null;
		if ( null === $valid ) {
			return new PartyValidationResult( PartyValidationResult::UNKNOWN, $vatNumber, '', '', 'VIES did not return a validity flag.' );
		}

		$name    = is_string( $data['name'] ?? null ) ? trim( $data['name'] ) : '';
		$address = is_string( $data['address'] ?? null ) ? trim( $data['address'] ) : '';

		return new PartyValidationResult(
			$valid ? PartyValidationResult::VALID : PartyValidationResult::INVALID,
			$vatNumber,
			$name,
			$address
		);
	}

	/**
	 * Loose comparison of the VIES-registered name to the name on the order.
	 * Returns true when they plausibly match (case/whitespace/legal-form noise
	 * removed). Used to WARN, not block — buyers legitimately trade under names
	 * that differ from their registered one.
	 */
	public static function namesPlausiblyMatch( string $official, string $onOrder ): bool {
		$normalise = static function ( string $s ): string {
			$s = strtolower( $s );
			$s = preg_replace( '/\b(bv|nv|sa|sarl|sas|gmbh|ltd|limited|bvba|sprl|srl|ag|inc)\b/u', '', $s ) ?? $s;
			$s = preg_replace( '/[^a-z0-9]/u', '', $s ) ?? $s;
			return $s;
		};

		$a = $normalise( $official );
		$b = $normalise( $onOrder );
		if ( '' === $a || '' === $b ) {
			return false;
		}

		return $a === $b || str_contains( $a, $b ) || str_contains( $b, $a );
	}
}
