<?php
/**
 * Auto-zakładanie / uzupełnianie kontrahentów z faktur pobranych z KSeF.
 *
 * - Z faktur ZAKUPU → kontrahentem jest SPRZEDAWCA (dostawca).
 * - Z faktur SPRZEDAŻY → kontrahentem jest NABYWCA (odbiorca).
 *
 * Idempotentne: deduplikacja po NIP (a gdy brak NIP — po nazwie). Istniejących
 * rekordów NIE nadpisujemy w polach, które użytkownik już wypełnił — uzupełniamy
 * tylko puste (np. adres dociągnięty później z pełnego XML).
 *
 * @package KSEF\WordPress\V2
 */

declare(strict_types=1);

namespace KSEF\WordPress\V2;

// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders, WordPress.DB.SlowDBQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Wlasne tabele gksef_*: wartosci przez $wpdb->prepare()/esc_like(), nazwy tabel z $wpdb->prefix, paginacja rzutowana (int); nonce weryfikowany w handlerze (check_ajax_referer/check_admin_referer).

class Contractors_Sync {

	/** Opcja: lista „nagrobków" — kontrahentów usuniętych ręcznie, których sync NIE odtwarza. */
	const TOMBSTONE_OPT = 'gksef_contractor_tombstones';

	/** Klucz nagrobka: po NIP (gdy jest), inaczej po nazwie. '' = brak danych. */
	public static function tombstone_key( string $nip, string $name ): string {
		$nip = (string) preg_replace( '/\D+/', '', $nip );
		if ( '' !== $nip ) {
			return 'nip:' . $nip;
		}
		$name = strtolower( trim( $name ) );
		return '' !== $name ? 'name:' . $name : '';
	}

	/** Czy kontrahent został ręcznie usunięty (i nie wolno go odtwarzać przy sync). */
	public static function is_tombstoned( string $nip, string $name ): bool {
		$key = self::tombstone_key( $nip, $name );
		if ( '' === $key ) {
			return false;
		}
		return in_array( $key, (array) get_option( self::TOMBSTONE_OPT, [] ), true );
	}

	/** Oznacz kontrahenta jako usuniętego (po ręcznym usunięciu z bazy). */
	public static function add_tombstone( string $nip, string $name ): void {
		$key = self::tombstone_key( $nip, $name );
		if ( '' === $key ) {
			return;
		}
		$list = (array) get_option( self::TOMBSTONE_OPT, [] );
		if ( ! in_array( $key, $list, true ) ) {
			$list[] = $key;
			update_option( self::TOMBSTONE_OPT, $list, false );
		}
	}

	/** Zdejmij nagrobek (gdy user świadomie dodaje/zapisuje kontrahenta ponownie). */
	public static function remove_tombstone( string $nip, string $name ): void {
		$key = self::tombstone_key( $nip, $name );
		if ( '' === $key ) {
			return;
		}
		$list = (array) get_option( self::TOMBSTONE_OPT, [] );
		if ( in_array( $key, $list, true ) ) {
			update_option( self::TOMBSTONE_OPT, array_values( array_diff( $list, [ $key ] ) ), false );
		}
	}

	/**
	 * Wrzuca/uzupełnia kontrahenta w gksef_contractor_mappings.
	 *
	 * @param array<string,mixed> $p Dane strony: name, nip, country_code, street,
	 *                                street_number, postal_code, city, email, vat_ue.
	 *                                Akceptuje też zagnieżdżony 'address' => [...].
	 */
	public static function upsert( array $p ): void {
		global $wpdb;
		$table = $wpdb->prefix . 'gksef_contractor_mappings';

		// Spłaszcz adres jeśli przyszedł zagnieżdżony (z Fa3_Parsera).
		$addr = is_array( $p['address'] ?? null ) ? $p['address'] : [];
		$name = trim( (string) ( $p['name'] ?? '' ) );
		$nip  = preg_replace( '/\D+/', '', (string) ( $p['nip'] ?? ( $p['identifier']['value'] ?? '' ) ) );
		$nip  = (string) $nip;

		// Nic sensownego do zapisania.
		if ( '' === $name && '' === $nip ) {
			return;
		}

		// Kontrahent usunięty ręcznie z bazy — NIE odtwarzaj go przy synchronizacji.
		if ( self::is_tombstoned( $nip, $name ) ) {
			return;
		}

		$country = strtoupper( (string) ( $p['country_code'] ?? $addr['country'] ?? 'PL' ) );
		$buyer_type = (string) ( $p['buyer_type'] ?? '' );
		if ( '' === $buyer_type ) {
			if ( 10 === strlen( $nip ) && 'PL' === $country ) {
				$buyer_type = 'pl_firm';
			} elseif ( '' !== $nip && 'PL' !== $country ) {
				$buyer_type = 'ue';
			} else {
				$buyer_type = $nip !== '' ? 'pl_firm' : 'b2c';
			}
		}

		// Kandydat danych (tylko niepuste).
		$incoming = array_filter( [
			'contractor_nip' => $nip,
			'contractor_name'=> $name,
			'country_code'   => $country,
			'vat_ue'         => (string) ( $p['vat_ue'] ?? '' ),
			'street'         => (string) ( $p['street'] ?? $addr['street'] ?? '' ),
			'street_number'  => (string) ( $p['street_number'] ?? $addr['street_number'] ?? '' ),
			'postal_code'    => (string) ( $p['postal_code'] ?? $addr['postal_code'] ?? '' ),
			'city'           => (string) ( $p['city'] ?? $addr['city'] ?? '' ),
			'email'          => (string) ( $p['email'] ?? '' ),
		], static fn( $v ) => '' !== (string) $v );

		// Znajdź istniejącego: po NIP, a gdy brak NIP — po dokładnej nazwie.
		$existing = null;
		if ( '' !== $nip ) {
			$existing = $wpdb->get_row( $wpdb->prepare(
				"SELECT * FROM {$table} WHERE contractor_nip = %s LIMIT 1", $nip
			), ARRAY_A );
		} elseif ( '' !== $name ) {
			$existing = $wpdb->get_row( $wpdb->prepare(
				"SELECT * FROM {$table} WHERE contractor_name = %s AND ( contractor_nip IS NULL OR contractor_nip = '' ) LIMIT 1", $name
			), ARRAY_A );
		}

		if ( $existing ) {
			// Uzupełnij TYLKO puste pola — nie nadpisuj danych wpisanych przez usera.
			$update = [];
			foreach ( $incoming as $col => $val ) {
				if ( '' === (string) ( $existing[ $col ] ?? '' ) && '' !== (string) $val ) {
					$update[ $col ] = $val;
				}
			}
			if ( ! empty( $update ) ) {
				$wpdb->update( $table, $update, [ 'id' => (int) $existing['id'] ] );
			}
			return;
		}

		// Nowy kontrahent.
		$incoming['buyer_type'] = $buyer_type;
		$wpdb->insert( $table, $incoming );
	}
}
