<?php
/**
 * Auto-trigger wysłania faktury do KSeF gdy zamówienie zmieni status.
 *
 * Konfiguracja w `gksef_credentials.auto_send_enabled` per NIP + global setting
 * `ksef_auto_send_trigger` (option: 'off'|'on_processing'|'on_completed').
 *
 * @package KSEF\WordPress\WC
 */

declare(strict_types=1);

namespace KSEF\WordPress\WC;

// 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).

use KSEF\WordPress\V2\Bootstrap as V2_Bootstrap;

final class Order_Trigger {

	public const OPTION_AUTO_TRIGGER  = 'ksef_auto_send_trigger';     // legacy (single)
	public const OPTION_AUTO_STATUSES = 'ksef_auto_send_statuses';    // array slugów WC
	public const OPTION_DEFAULT_CRED  = 'ksef_default_credential_id';
	public const OPTION_PRIVATE_MODE  = 'ksef_private_customer_mode'; // 'fa_imienna' | 'skip'
	public const OPTION_AUTO_EMAIL    = 'ksef_auto_email_pdf';        // '1' = wyślij PDF na maila po wystawieniu

	/**
	 * Lista statusów WC (slugi bez prefiksu wc-), które wyzwalają auto-wystawianie.
	 * Łączy nowy multi-select z legacy pojedynczym ustawieniem.
	 *
	 * @return array<int,string>
	 */
	public static function trigger_statuses(): array {
		$statuses = (array) get_option( self::OPTION_AUTO_STATUSES, [] );
		$statuses = array_filter( array_map( 'strval', $statuses ) );
		// Legacy fallback — pojedyncze 'on_<status>'
		if ( empty( $statuses ) ) {
			$legacy = (string) get_option( self::OPTION_AUTO_TRIGGER, 'off' );
			if ( 'off' !== $legacy && preg_match( '/^on_(.+)$/', $legacy, $m ) ) {
				$statuses = [ $m[1] ];
			}
		}
		return array_values( array_unique( $statuses ) );
	}

	public static function register(): void {
		// Generyczny hook — łapie KAŻDĄ zmianę statusu i porównuje z wybranym
		add_action( 'woocommerce_order_status_changed', [ self::class, 'on_status_changed' ], 20, 4 );
		add_action( 'ksef_send_invoice_for_order',      [ self::class, 'send_now' ], 10, 2 );
	}

	public static function on_status_changed( int $order_id, string $from, string $to, \WC_Order $order ): void {
		$statuses = self::trigger_statuses();
		if ( empty( $statuses ) || ! in_array( $to, $statuses, true ) ) {
			return;
		}
		self::dispatch( $order_id, $order, 'on_' . $to );
	}

	public static function maybe_send_on_processing( int $order_id, \WC_Order $order ): void {
		if ( get_option( self::OPTION_AUTO_TRIGGER, 'off' ) !== 'on_processing' ) {
			return;
		}
		self::dispatch( $order_id, $order, 'on_processing' );
	}

	public static function maybe_send_on_completed( int $order_id, \WC_Order $order ): void {
		if ( get_option( self::OPTION_AUTO_TRIGGER, 'off' ) !== 'on_completed' ) {
			return;
		}
		self::dispatch( $order_id, $order, 'on_completed' );
	}

	/**
	 * Czy wysyłać fakturę do KSeF automatycznie dla danego typu nabywcy?
	 * Wg opcji ksef_auto_send_policy: '' = wyłączone | 'b2b' (firmy) | 'b2c' | 'all'.
	 */
	private static function ksef_send_allowed( bool $is_business ): bool {
		$policy = (string) get_option( 'ksef_auto_send_policy', '' );
		if ( 'all' === $policy ) {
			return true;
		}
		if ( 'b2b' === $policy ) {
			return $is_business;
		}
		if ( 'b2c' === $policy ) {
			return ! $is_business;
		}
		return false; // '' (Wyłączone) lub nieznane
	}

	/**
	 * Czy w ogóle WYSTAWIAĆ fakturę z zamówienia? Firmy zawsze; B2C wg „Polityki dla
	 * klientów prywatnych": skip = nie; fa_imienna = tylko na żądanie; fa_imienna_all = zawsze.
	 */
	private static function should_issue( \WC_Order $order, bool $is_business ): bool {
		if ( $is_business ) {
			return true;
		}
		$pm = (string) get_option( self::OPTION_PRIVATE_MODE, 'fa_imienna' );
		if ( 'skip' === $pm ) {
			return false;
		}
		if ( 'fa_imienna_all' === $pm ) {
			return true;
		}
		return (bool) $order->get_meta( '_ksef_request_invoice' ); // 'fa_imienna' — tylko na żądanie
	}

	private static function dispatch( int $order_id, \WC_Order $order, string $trigger ): void {
		$status = (string) $order->get_meta( '_ksef_invoice_status' );
		// Już wysłana / w trakcie w KSeF — nie duplikuj.
		if ( in_array( $status, [ 'sent', 'pending', 'confirmed' ], true ) ) {
			return;
		}

		$is_business = Checkout_Field::is_business_order( $order );

		// Faktura wystawiona już LOKALNIE i czekająca (status „issued") — doślij do KSeF, jeśli polityka
		// auto-wysyłki teraz na to pozwala (np. po zmianie „Auto-wysyłka do KSeF" na „Wszystkie").
		// send_now (ścieżka A) wyśle istniejący dokument bez duplikatu. Inaczej zostaje lokalna.
		if ( 'issued' === $status ) {
			if ( self::ksef_send_allowed( $is_business ) ) {
				self::enqueue_send( $order_id, $trigger );
			}
			return;
		}
		// Inne stany już tknięte lokalnie (np. „failed") — bez automatu, użytkownik wyśle ręcznie.
		if ( '1' === (string) $order->get_meta( '_ksef_invoice_local_issued' ) ) {
			return;
		}

		if ( ! self::should_issue( $order, $is_business ) ) {
			$order->add_order_note( __( 'KSeF: pominięto wystawienie — klient prywatny bez żądania faktury (Polityka dla klientów prywatnych).', 'prostafaktura-ksef' ) );
			return;
		}

		// ZAWSZE wystaw (lokalnie); wysyłka do KSeF wg polityki rozstrzyga się w send_now.
		self::enqueue_send( $order_id, $trigger );
		// translators: %s = trigger
		$order->add_order_note( sprintf( __( 'KSeF: zakolejkowano wystawienie faktury (trigger: %s).', 'prostafaktura-ksef' ), $trigger ) );
	}

	/** Zakolejkuj wystawienie/wysyłkę faktury dla zamówienia (Action Scheduler lub WP-Cron). */
	private static function enqueue_send( int $order_id, string $trigger ): void {
		if ( function_exists( 'as_enqueue_async_action' ) ) {
			as_enqueue_async_action( 'ksef_send_invoice_for_order', [ 'order_id' => $order_id, 'trigger' => $trigger ], 'ksef' );
		} else {
			wp_schedule_single_event( time() + 5, 'ksef_send_invoice_for_order', [ $order_id, $trigger ] );
		}
	}

	/**
	 * Wystawienie/wysyłka — z kolejki (auto) lub ręcznie z metaboxa (trigger='manual').
	 * WYSTAWIENIE następuje zawsze; wysyłka do KSeF zależy od polityki (auto) lub jest
	 * wymuszona (manual). Gdy auto-wysyłka dla typu wyłączona → faktura wystawiona lokalnie.
	 *
	 * @return array<string,mixed>|\WP_Error
	 */
	public static function send_now( int $order_id, string $trigger = 'manual' ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return new \WP_Error( 'order_not_found', __( 'Nie znaleziono zamówienia.', 'prostafaktura-ksef' ) );
		}
		$credential = self::resolve_credential( $order );
		if ( ! $credential ) {
			return new \WP_Error( 'no_credential', __( 'Brak skonfigurowanego kredencjału KSeF.', 'prostafaktura-ksef' ) );
		}

		$is_business = Checkout_Field::is_business_order( $order );
		$manual      = ( 'manual' === $trigger );
		$sender      = V2_Bootstrap::invoice_sender_for_credential( $credential );

		// (A) Faktura już wystawiona LOKALNIE → doślij ją do KSeF (bez duplikatu).
		if ( '1' === (string) $order->get_meta( '_ksef_invoice_local_issued' )
			&& 'sent' !== (string) $order->get_meta( '_ksef_invoice_status' ) ) {
			global $wpdb;
			$issued_id = (int) $wpdb->get_var( $wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}gksef_outgoing_invoices WHERE wc_order_id = %d AND status = 'issued' ORDER BY id DESC LIMIT 1",
				$order_id
			) );
			if ( $issued_id > 0 ) {
				$result = $sender->send_draft( $issued_id, $credential );
				self::apply_ksef_send_result( $order, $result );
				$order->save();
				return $result;
			}
		}

		// (B) Wysyłka do KSeF: ręcznie zawsze; auto — tylko gdy polityka pozwala dla typu nabywcy.
		if ( $manual || self::ksef_send_allowed( $is_business ) ) {
			$result = $sender->send( $order, $credential );
			if ( is_wp_error( $result ) ) {
				// KSeF nie przyjął faktury → wystaw ją LOKALNIE, żeby istniała jako dokument do
				// poprawy i ręcznej wysyłki. Gdy nie da się zbudować nawet lokalnie (np. błąd
				// danych/XSD) — zostaje stan „failed" (jak dotąd).
				$local = $sender->issue_local( $order, $credential );
				if ( is_wp_error( $local ) ) {
					self::apply_ksef_send_result( $order, $result, ! $manual );
				} else {
					$order->update_meta_data( '_ksef_invoice_error', $result->get_error_message() );
					// translators: %s = błąd
					$order->add_order_note( sprintf( __( 'KSeF: faktura wystawiona lokalnie, ale wysyłka do KSeF nie powiodła się — %s. Popraw przyczynę i wyślij ręcznie („Wyślij do KSeF").', 'prostafaktura-ksef' ), $result->get_error_message() ) );
					if ( ! $manual && class_exists( '\\KSEF\\WordPress\\Email_Notifier' ) ) {
						\KSEF\WordPress\Email_Notifier::send_invoice_send_failed( $order, $result->get_error_message(), (string) $order->get_meta( '_ksef_invoice_number' ) );
					}
				}
			} else {
				self::apply_ksef_send_result( $order, $result, ! $manual );
			}
			$order->save();
			return $result;
		}

		// (C) Auto + wysyłka do KSeF wyłączona dla typu → WYSTAW LOKALNIE (bez wysyłki do KSeF).
		$result = $sender->issue_local( $order, $credential );
		if ( is_wp_error( $result ) ) {
			// translators: %s = błąd
			$order->add_order_note( sprintf( __( 'KSeF: nie udało się wystawić faktury — %s', 'prostafaktura-ksef' ), $result->get_error_message() ) );
		} else {
			$order->update_meta_data( '_ksef_invoice_local_issued', '1' );
			$order->update_meta_data( '_ksef_invoice_status', 'issued' );
			$order->add_order_note( __( 'KSeF: faktura wystawiona lokalnie — auto-wysyłka do KSeF wyłączona dla tego nabywcy (wyślij ręcznie z metaboxa/listy faktur).', 'prostafaktura-ksef' ) );
			self::maybe_email_pdf( $order );
		}
		$order->save();
		return $result;
	}

	/**
	 * Po (próbie) wysyłki do KSeF — ustaw meta/notatki + auto-mail PDF.
	 *
	 * @param array<string,mixed>|\WP_Error $result
	 */
	private static function apply_ksef_send_result( \WC_Order $order, $result, bool $auto = false ): void {
		if ( is_wp_error( $result ) ) {
			$order->update_meta_data( '_ksef_invoice_status', 'failed' );
			$order->update_meta_data( '_ksef_invoice_error', $result->get_error_message() );
			$number = (string) $order->get_meta( '_ksef_invoice_number' );
			// translators: %s = błąd
			$note = sprintf( __( 'KSeF: wystawienie nie powiodło się — %s', 'prostafaktura-ksef' ), $result->get_error_message() );
			if ( '' !== $number ) {
				// translators: %s = numer faktury
				$note .= ' ' . sprintf( __( '(numer zarezerwowany: %s — użyj „Ponów wysyłkę")', 'prostafaktura-ksef' ), $number );
			}
			$order->add_order_note( $note );
			// Auto-wysyłka, która zawiodła → powiadom administratora mailem (dokładny błąd + jak naprawić).
			if ( $auto && class_exists( '\\KSEF\\WordPress\\Email_Notifier' ) ) {
				\KSEF\WordPress\Email_Notifier::send_invoice_send_failed( $order, $result->get_error_message(), $number );
			}
			return;
		}
		$order->update_meta_data( '_ksef_invoice_local_issued', '1' );
		$order->update_meta_data( '_ksef_session_reference', (string) ( $result['session_reference'] ?? '' ) );
		$order->update_meta_data( '_ksef_element_reference', (string) ( $result['invoice_reference'] ?? '' ) );

		// Status finalny z natychmiastowego potwierdzania (confirm_now w Invoice_Sender). Gdy MF
		// już potwierdził/odrzucił — ustaw od razu (nie cofaj do 'sent'); inaczej 'sent' + zaplanuj
		// szybkie próby sprawdzenia statusu (zamiast czekać na 15-min cron).
		$final = (string) ( $result['final_status'] ?? $result['status'] ?? 'sent' );
		if ( 'confirmed' === $final ) {
			$order->update_meta_data( '_ksef_invoice_status', 'confirmed' );
			if ( ! empty( $result['ksef_number'] ) ) {
				$order->update_meta_data( '_ksef_reference_number', (string) $result['ksef_number'] );
			}
			$order->add_order_note( __( 'KSeF: faktura zatwierdzona — pobrano UPO i numer KSeF.', 'prostafaktura-ksef' ) );
		} elseif ( 'rejected' === $final ) {
			$order->update_meta_data( '_ksef_invoice_status', 'rejected' );
			$order->add_order_note( __( 'KSeF: faktura odrzucona przez KSeF.', 'prostafaktura-ksef' ) );
		} else {
			$order->update_meta_data( '_ksef_invoice_status', 'sent' );
			$order->add_order_note( __( 'KSeF: faktura wysłana — czekamy na potwierdzenie (UPO).', 'prostafaktura-ksef' ) );
			self::schedule_fast_status_checks( (int) $order->get_id() );
		}
		self::maybe_email_pdf( $order );
	}

	/**
	 * Szybkie auto-potwierdzanie statusu po wysyłce — kilka prób narastająco (Action Scheduler:
	 * natychmiast + 60/180/300/600 s), zamiast czekać na 15-min cron. Każda próba odpytuje status;
	 * gdy confirmed/rejected — kolejne próby same się pomijają. Cykliczny poll zostaje jako siatka.
	 */
	private static function schedule_fast_status_checks( int $order_id ): void {
		if ( $order_id <= 0 ) {
			return;
		}
		if ( function_exists( 'as_enqueue_async_action' ) ) {
			$args = [ 'order_id' => $order_id ];
			// Idempotencja — nie duplikuj zaplanowanych prób dla tego zamówienia.
			if ( function_exists( 'as_has_scheduled_action' ) && as_has_scheduled_action( 'ksef_check_status_once', $args, 'ksef' ) ) {
				return;
			}
			as_enqueue_async_action( 'ksef_check_status_once', $args, 'ksef' );
			foreach ( [ 60, 180, 300, 600 ] as $delay ) {
				as_schedule_single_action( time() + $delay, 'ksef_check_status_once', $args, 'ksef' );
			}
		} else {
			foreach ( [ 10, 60, 180, 300, 600 ] as $delay ) {
				if ( ! wp_next_scheduled( 'ksef_check_status_once', [ $order_id ] ) ) {
					wp_schedule_single_event( time() + $delay, 'ksef_check_status_once', [ $order_id ] );
				}
			}
		}
	}

	/**
	 * Handler pojedynczej szybkiej próby sprawdzenia statusu (Action Scheduler / cron).
	 * Idempotentny: pomija, gdy zamówienie już confirmed/rejected. Używa tej samej logiki co
	 * ręczne „Sprawdź status" (run_poll_cron → finalize_invoice ustawia status/UPO/numer KSeF).
	 *
	 * @param int $order_id
	 */
	public static function check_status_once( $order_id = 0 ): void {
		$order = wc_get_order( (int) $order_id );
		if ( ! $order ) {
			return;
		}
		$status = (string) $order->get_meta( '_ksef_invoice_status' );
		if ( in_array( $status, [ 'confirmed', 'rejected' ], true ) ) {
			return; // już gotowe — nie odpytuj
		}
		if ( class_exists( '\\KSEF\\WordPress\\V2\\Bootstrap' ) ) {
			\KSEF\WordPress\V2\Bootstrap::run_poll_cron();
		}
	}

	/**
	 * Auto-mail PDF faktury do nabywcy (gdy włączone w Ustawieniach i znamy e-mail).
	 */
	private static function maybe_email_pdf( \WC_Order $order ): void {
		if ( '1' !== (string) get_option( self::OPTION_AUTO_EMAIL, '1' ) ) {
			return;
		}
		$email = (string) $order->get_billing_email();
		if ( ! is_email( $email ) ) {
			return;
		}
		global $wpdb;
		$invoice_id = (int) $wpdb->get_var( $wpdb->prepare(
			"SELECT id FROM {$wpdb->prefix}gksef_outgoing_invoices WHERE wc_order_id = %d ORDER BY id DESC LIMIT 1",
			(int) $order->get_id()
		) );
		if ( $invoice_id <= 0 ) {
			return;
		}
		require_once KSEF_WORDPRESS_PATH . 'includes/class-invoice-pdf.php';
		$res = \KSEF\WordPress\Invoice_PDF::send_email( $invoice_id, $email );
		if ( is_wp_error( $res ) ) {
			// translators: %1$s = email, %2$s = błąd
			$order->add_order_note( sprintf( __( 'KSeF: nie udało się wysłać PDF na maila (%1$s): %2$s', 'prostafaktura-ksef' ), $email, $res->get_error_message() ) );
		} else {
			// translators: %s = email
			$order->add_order_note( sprintf( __( 'KSeF: PDF faktury wysłany na %s', 'prostafaktura-ksef' ), $email ) );
		}
	}

	/**
	 * Wybiera kredencjał KSeF do użycia dla danego zamówienia.
	 * Strategia: jawnie ustawione w order meta → domyślne z opcji → pierwszy aktywny.
	 */
	private static function resolve_credential( \WC_Order $order ): ?array {
		global $wpdb;
		$table = $wpdb->prefix . 'gksef_credentials';

		$id_from_order = (int) $order->get_meta( '_ksef_credential_id' );
		if ( $id_from_order > 0 ) {
			$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id_from_order ), ARRAY_A );
			if ( $row ) {
				return $row;
			}
		}

		$default_id = (int) get_option( self::OPTION_DEFAULT_CRED, 0 );
		if ( $default_id > 0 ) {
			$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $default_id ), ARRAY_A );
			if ( $row ) {
				return $row;
			}
		}

		$row = $wpdb->get_row( "SELECT * FROM {$table} ORDER BY id ASC LIMIT 1", ARRAY_A );
		return $row ?: null;
	}
}
