<?php
/**
 * Klasa instalatora — tworzenie tabel przy aktywacji
 *
 * @package KSEF\WordPress
 */

namespace KSEF\WordPress;

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

/**
 * Installer class
 */
class Installer {

	/**
	 * Instaluj wtyczkę
	 *
	 * @return void
	 */
	public static function install() {
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		global $wpdb;

		$charset_collate = $wpdb->get_charset_collate();

		// Tabela: gksef_licenses
		$table_licenses = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_licenses (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			license_key varchar(64) NOT NULL UNIQUE,
			license_status varchar(32) NOT NULL DEFAULT 'inactive',
			activation_date datetime DEFAULT NULL,
			expiration_date datetime DEFAULT NULL,
			max_sites int(11) DEFAULT 1,
			active_sites longtext,
			product_type varchar(32) DEFAULT 'starter',
			last_check_date datetime DEFAULT NULL,
			next_check_date datetime DEFAULT NULL,
			activation_code varchar(255) DEFAULT NULL,
			is_lifetime tinyint(1) DEFAULT 0,
			notes text,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			INDEX idx_license_key (license_key),
			INDEX idx_status (license_status)
		) $charset_collate;";

		dbDelta( $table_licenses );

		// Tabela: gksef_credentials (ustawienia KSeF)
		$table_credentials = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_credentials (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			name varchar(255) NOT NULL,
			nip varchar(10) NOT NULL,
			token_encrypted longtext,
			auth_method varchar(32) DEFAULT 'token',
			certificate_pem longtext,
			private_key_pem_encrypted longtext,
			certificate_subject varchar(255),
			certificate_issuer varchar(255),
			certificate_serial varchar(255),
			certificate_valid_from datetime,
			certificate_valid_to datetime,
			token_valid_to datetime DEFAULT NULL,
			ksef_environment varchar(32) DEFAULT 'test',
			is_production tinyint(1) DEFAULT 0,
			auto_sync_enabled tinyint(1) DEFAULT 0,
			auto_sync_interval_hours int(11) DEFAULT 24,
			auto_send_enabled tinyint(1) DEFAULT 0,
			auto_create_expenses tinyint(1) DEFAULT 0,
			last_sync_at datetime DEFAULT NULL,
			sync_incoming tinyint(1) NOT NULL DEFAULT 1,
			sync_outgoing tinyint(1) NOT NULL DEFAULT 0,
			last_outgoing_sync_at datetime DEFAULT NULL,
			seller_name varchar(255) NOT NULL DEFAULT '',
			seller_email varchar(190) NOT NULL DEFAULT '',
			seller_phone varchar(40) NOT NULL DEFAULT '',
			seller_street varchar(255) NOT NULL DEFAULT '',
			seller_building varchar(32) NOT NULL DEFAULT '',
			seller_postal varchar(20) NOT NULL DEFAULT '',
			seller_city varchar(120) NOT NULL DEFAULT '',
			seller_country varchar(3) NOT NULL DEFAULT 'PL',
			hwm_part_key varchar(255),
			last_hwm_element_reference varchar(255),
			last_hwm_timestamp varchar(255),
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			INDEX idx_nip (nip),
			INDEX idx_environment (ksef_environment)
		) $charset_collate;";

		dbDelta( $table_credentials );

		// ALTER TABLE dla credentials — dbDelta nie zawsze dodaje nowe kolumny do
		// istniejącej tabeli (quirk z porównywaniem definicji). Twardy ADD COLUMN.
		self::ensure_credentials_columns();

		// Tabela: gksef_outgoing_invoices (faktury wysłane)
		// wc_order_id zmienione na 0 jako default dla faktur ręcznych (poza WC)
		$table_outgoing = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_outgoing_invoices (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			wc_order_id bigint(20) NOT NULL DEFAULT 0,
			credential_id bigint(20) NOT NULL,
			status varchar(32) DEFAULT 'pending',
			invoice_number varchar(80) DEFAULT '',
			seller_nip varchar(20) DEFAULT '',
			buyer_nip varchar(20) DEFAULT '',
			buyer_name varchar(255) DEFAULT '',
			gross_amount decimal(15,2) DEFAULT 0,
			net_amount decimal(15,2) DEFAULT 0,
			vat_amount decimal(15,2) DEFAULT 0,
			currency char(3) DEFAULT 'PLN',
			ksef_reference_number varchar(64),
			ksef_session_id varchar(255),
			generated_xml longtext,
			submission_timestamp datetime DEFAULT NULL,
			retry_count int(11) DEFAULT 0,
			offline_reference_number varchar(255),
			offline_reason text,
			upo_received_at datetime DEFAULT NULL,
			upo_reference varchar(255),
			upo_xml longtext,
			dedupe_hash varchar(64) DEFAULT NULL,
			last_status_check datetime DEFAULT NULL,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			INDEX idx_wc_order (wc_order_id),
			INDEX idx_status (status),
			INDEX idx_ksef_ref (ksef_reference_number),
			INDEX idx_seller_nip (seller_nip),
			FOREIGN KEY (credential_id) REFERENCES {$wpdb->prefix}gksef_credentials(id) ON DELETE CASCADE
		) $charset_collate;";

		dbDelta( $table_outgoing );

		// ALTER TABLE dla istniejących instalacji — dorzuca brakujące kolumny
		self::ensure_outgoing_columns();

		// Tabela: gksef_invoice_payments (płatności za fakturę — możliwych wiele rat)
		$table_payments = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_invoice_payments (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			invoice_id bigint(20) NOT NULL,
			amount decimal(15,2) NOT NULL,
			currency char(3) DEFAULT 'PLN',
			payment_date date NOT NULL,
			payment_method varchar(64) DEFAULT '',
			reference varchar(255) DEFAULT '',
			note text,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_invoice (invoice_id),
			INDEX idx_payment_date (payment_date),
			FOREIGN KEY (invoice_id) REFERENCES {$wpdb->prefix}gksef_outgoing_invoices(id) ON DELETE CASCADE
		) $charset_collate;";

		dbDelta( $table_payments );

		// Tabela: gksef_incoming_invoices (faktury pobrane z KSeF)
		$table_incoming = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_incoming_invoices (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			credential_id bigint(20) NOT NULL,
			ksef_reference_number varchar(64) NOT NULL UNIQUE,
			invoice_number varchar(64),
			invoice_type varchar(32),
			issue_date date,
			seller_nip varchar(10),
			seller_name varchar(255),
			buyer_nip varchar(10),
			net_amount decimal(15,2),
			vat_amount decimal(15,2),
			gross_amount decimal(15,2),
			processing_status varchar(32) DEFAULT 'pending',
			raw_xml longtext,
			synced_at datetime DEFAULT NULL,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_ksef_ref (ksef_reference_number),
			INDEX idx_seller_nip (seller_nip),
			INDEX idx_issue_date (issue_date),
			FOREIGN KEY (credential_id) REFERENCES {$wpdb->prefix}gksef_credentials(id) ON DELETE CASCADE
		) $charset_collate;";

		dbDelta( $table_incoming );

		// ALTER TABLE dla incoming — dorzuca brakujące kolumny (payment_status, due_date, paid_date, category)
		self::ensure_incoming_columns();

		// Tabela: gksef_contractor_mappings (biblioteka kontrahentów — pełne dane do faktury)
		$table_mappings = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_contractor_mappings (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			contractor_nip varchar(20),
			contractor_name varchar(255),
			buyer_type varchar(16) DEFAULT 'pl_firm',
			country_code varchar(3) DEFAULT 'PL',
			vat_ue varchar(32),
			nr_id varchar(64),
			street varchar(255),
			street_number varchar(32),
			postal_code varchar(20),
			city varchar(120),
			email varchar(190),
			phone varchar(40),
			customer_no varchar(64),
			default_category varchar(100),
			default_gtu varchar(10),
			notes text,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			INDEX idx_nip (contractor_nip),
			INDEX idx_name (contractor_name)
		) $charset_collate;";

		dbDelta( $table_mappings );

		// Tabela: gksef_bank_accounts (rachunki bankowe sprzedawcy per kredencjał)
		$table_banks = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_bank_accounts (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			credential_id bigint(20) NOT NULL,
			account_number varchar(64) NOT NULL,
			bank_name varchar(255),
			swift varchar(32),
			currency char(3) DEFAULT 'PLN',
			is_default tinyint(1) DEFAULT 0,
			display_order int(11) DEFAULT 0,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_credential (credential_id)
		) $charset_collate;";

		dbDelta( $table_banks );

		// Tabela: gksef_sync_log (historia synchronizacji)
		$table_sync_log = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_sync_log (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			credential_id bigint(20) NOT NULL,
			sync_type varchar(32),
			sync_direction varchar(32),
			status varchar(32) DEFAULT 'started',
			started_at datetime,
			completed_at datetime DEFAULT NULL,
			invoices_processed int(11) DEFAULT 0,
			invoices_success int(11) DEFAULT 0,
			invoices_failed int(11) DEFAULT 0,
			error_message text,
			date_from date,
			date_to date,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_credential (credential_id),
			INDEX idx_status (status),
			FOREIGN KEY (credential_id) REFERENCES {$wpdb->prefix}gksef_credentials(id) ON DELETE CASCADE
		) $charset_collate;";

		dbDelta( $table_sync_log );

		// Tabela: gksef_invoice_attachments (załączniki użytkownika do faktur).
		// Pliki przechowywane w PRYWATNYM katalogu (poza uploads/), serwowane
		// tylko przez endpoint ksef_attachment_download z auth-check + nonce.
		$table_attachments = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_invoice_attachments (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			invoice_type varchar(16) NOT NULL DEFAULT 'incoming',
			invoice_id bigint(20) NOT NULL,
			file_path varchar(500) NOT NULL,
			original_name varchar(255) NOT NULL,
			mime_type varchar(120) NULL,
			size_bytes bigint(20) NULL,
			label varchar(255) NULL,
			uploaded_by bigint(20) NULL,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_invoice (invoice_type, invoice_id)
		) $charset_collate;";
		dbDelta( $table_attachments );

		// Tabela: gksef_incoming_payments (płatności za faktury zakupowe).
		// Wieloratowe płatności + powiązanie z gksef_incoming_invoices.
		$table_inc_payments = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}gksef_incoming_payments (
			id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
			incoming_invoice_id bigint(20) NOT NULL,
			amount decimal(15,2) NOT NULL DEFAULT 0,
			currency char(3) NOT NULL DEFAULT 'PLN',
			payment_date date NOT NULL,
			payment_method varchar(64) NULL,
			reference varchar(255) NULL,
			note text NULL,
			created_by bigint(20) NULL,
			created_at datetime DEFAULT CURRENT_TIMESTAMP,
			INDEX idx_invoice (incoming_invoice_id),
			INDEX idx_date (payment_date)
		) $charset_collate;";
		dbDelta( $table_inc_payments );
	}

	/**
	 * ALTER TABLE — dorzuca brakujące kolumny do gksef_outgoing_invoices.
	 * Bezpieczne wywoływać wielokrotnie (sprawdza existence).
	 */
	public static function ensure_credentials_columns(): void {
		global $wpdb;
		$table = $wpdb->prefix . 'gksef_credentials';
		$existing = $wpdb->get_col( "SHOW COLUMNS FROM {$table}" );
		if ( empty( $existing ) ) {
			return;
		}
		$existing = array_map( 'strtolower', $existing );

		$wanted = [
			'token_valid_to' => "ADD COLUMN token_valid_to datetime DEFAULT NULL AFTER certificate_valid_to",
		];
		foreach ( $wanted as $col => $sql ) {
			if ( ! in_array( $col, $existing, true ) ) {
				$wpdb->query( "ALTER TABLE {$table} " . $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- nazwy tabel z $wpdb->prefix, wartosci przez $wpdb->prepare().
			}
		}
	}

	public static function ensure_incoming_columns(): void {
		global $wpdb;
		$table = $wpdb->prefix . 'gksef_incoming_invoices';
		$existing = $wpdb->get_col( "SHOW COLUMNS FROM {$table}" );
		if ( empty( $existing ) ) {
			return;
		}
		$existing = array_map( 'strtolower', $existing );

		$wanted = [
			'currency'        => "ADD COLUMN currency char(3) NOT NULL DEFAULT 'PLN' AFTER gross_amount",
			'due_date'        => "ADD COLUMN due_date date NULL AFTER issue_date",
			'payment_status'  => "ADD COLUMN payment_status varchar(16) NOT NULL DEFAULT 'unpaid' AFTER due_date",
			'paid_date'       => "ADD COLUMN paid_date date NULL AFTER payment_status",
			'payment_method'  => "ADD COLUMN payment_method varchar(64) NULL AFTER paid_date",
			'category'        => "ADD COLUMN category varchar(64) NULL",
			'pdf_path'        => "ADD COLUMN pdf_path varchar(500) NULL",
			'verified'        => "ADD COLUMN verified tinyint(1) NOT NULL DEFAULT 0",
		];
		foreach ( $wanted as $col => $sql ) {
			if ( ! in_array( $col, $existing, true ) ) {
				$wpdb->query( "ALTER TABLE {$table} " . $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- nazwy tabel z $wpdb->prefix, wartosci przez $wpdb->prepare().
			}
		}
	}

	public static function ensure_outgoing_columns(): void {
		global $wpdb;
		$table = $wpdb->prefix . 'gksef_outgoing_invoices';

		$existing = $wpdb->get_col( "SHOW COLUMNS FROM {$table}" );
		if ( empty( $existing ) ) {
			return; // tabela jeszcze nie istnieje
		}
		$existing = array_map( 'strtolower', $existing );

		$wanted = [
			'invoice_number'      => "ADD COLUMN invoice_number varchar(80) DEFAULT '' AFTER status",
			'seller_nip'          => "ADD COLUMN seller_nip varchar(20) DEFAULT '' AFTER invoice_number",
			'buyer_nip'           => "ADD COLUMN buyer_nip varchar(20) DEFAULT '' AFTER seller_nip",
			'buyer_name'          => "ADD COLUMN buyer_name varchar(255) DEFAULT '' AFTER buyer_nip",
			'gross_amount'        => "ADD COLUMN gross_amount decimal(15,2) DEFAULT 0 AFTER buyer_name",
			'net_amount'          => "ADD COLUMN net_amount decimal(15,2) DEFAULT 0 AFTER gross_amount",
			'vat_amount'          => "ADD COLUMN vat_amount decimal(15,2) DEFAULT 0 AFTER net_amount",
			'currency'            => "ADD COLUMN currency char(3) DEFAULT 'PLN' AFTER vat_amount",
			'payload_json'        => "ADD COLUMN payload_json longtext NULL AFTER generated_xml",
			// schemat v4: status płatności + termin + dodatkowe pola
			'due_date'            => "ADD COLUMN due_date date NULL AFTER currency",
			'payment_status'      => "ADD COLUMN payment_status varchar(16) NOT NULL DEFAULT 'unpaid' AFTER due_date",
			'paid_amount'         => "ADD COLUMN paid_amount decimal(15,2) NOT NULL DEFAULT 0 AFTER payment_status",
			'paid_date'           => "ADD COLUMN paid_date date NULL AFTER paid_amount",
			'notes'               => "ADD COLUMN notes text NULL",
			'additional_markers'  => "ADD COLUMN additional_markers longtext NULL",
			'related_docs'        => "ADD COLUMN related_docs longtext NULL",
			'extra_data'          => "ADD COLUMN extra_data longtext NULL",
			'delivery_address'    => "ADD COLUMN delivery_address longtext NULL",
			'pdf_path'            => "ADD COLUMN pdf_path varchar(500) NULL",
			'pdf_generated_at'    => "ADD COLUMN pdf_generated_at datetime NULL",
			'pdf_sent_at'         => "ADD COLUMN pdf_sent_at datetime NULL",
			'pdf_sent_to'         => "ADD COLUMN pdf_sent_to varchar(255) NULL",
			// v5
			'deposit_amount'      => "ADD COLUMN deposit_amount decimal(15,2) DEFAULT 0",
			'deposit_label'       => "ADD COLUMN deposit_label varchar(255) NULL",
			'bank_account_id'     => "ADD COLUMN bank_account_id bigint(20) NULL",
			'contractor_id'       => "ADD COLUMN contractor_id bigint(20) NULL",
			'internal_notes'      => "ADD COLUMN internal_notes text NULL",
			'ksef_custom_notes'   => "ADD COLUMN ksef_custom_notes longtext NULL",
			// v6
			'source'              => "ADD COLUMN source varchar(32) NOT NULL DEFAULT 'local'",
			'external_issuer'     => "ADD COLUMN external_issuer varchar(255) NULL",
			'pulled_at'           => "ADD COLUMN pulled_at datetime NULL",
			// v7 — kategoryzacja faktur
			'category'            => "ADD COLUMN category varchar(64) NULL",
			// v8 — niezawodność wysyłki: UPO (dowód), dedupe (anty-duplikat), znacznik ostatniego sprawdzenia
			'upo_xml'             => "ADD COLUMN upo_xml longtext NULL",
			'dedupe_hash'         => "ADD COLUMN dedupe_hash varchar(64) NULL",
			'last_status_check'   => "ADD COLUMN last_status_check datetime NULL",
			// v9 — autor faktury (do powiadomień) + znacznik wysłanego ostrzeżenia o braku potwierdzenia
			'created_by'           => "ADD COLUMN created_by bigint(20) NULL",
			'unconfirmed_notified' => "ADD COLUMN unconfirmed_notified tinyint(1) NOT NULL DEFAULT 0",
		];
		foreach ( $wanted as $col => $sql ) {
			if ( ! in_array( $col, $existing, true ) ) {
				$wpdb->query( "ALTER TABLE {$table} " . $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- nazwy tabel z $wpdb->prefix, wartosci przez $wpdb->prepare().
			}
		}

		// Zmień wc_order_id na DEFAULT 0 (dla faktur ręcznych)
		$wpdb->query( "ALTER TABLE {$table} MODIFY COLUMN wc_order_id bigint(20) NOT NULL DEFAULT 0" );

		// v5 — rozszerz contractor_mappings (gdy istnieje stara) o pełne pola adresowe
		$contractors = $wpdb->prefix . 'gksef_contractor_mappings';
		$existing_cm = $wpdb->get_col( "SHOW COLUMNS FROM {$contractors}" );
		if ( ! empty( $existing_cm ) ) {
			$existing_cm = array_map( 'strtolower', $existing_cm );
			$add_cm = [
				'buyer_type'    => "ADD COLUMN buyer_type varchar(16) DEFAULT 'pl_firm'",
				'country_code'  => "ADD COLUMN country_code varchar(3) DEFAULT 'PL'",
				'vat_ue'        => "ADD COLUMN vat_ue varchar(32)",
				'nr_id'         => "ADD COLUMN nr_id varchar(64)",
				'street'        => "ADD COLUMN street varchar(255)",
				'street_number' => "ADD COLUMN street_number varchar(32)",
				'postal_code'   => "ADD COLUMN postal_code varchar(20)",
				'city'          => "ADD COLUMN city varchar(120)",
				'email'         => "ADD COLUMN email varchar(190)",
				'phone'         => "ADD COLUMN phone varchar(40)",
				'customer_no'   => "ADD COLUMN customer_no varchar(64)",
				'default_gtu'   => "ADD COLUMN default_gtu varchar(10)",
				'notes'         => "ADD COLUMN notes text",
				'updated_at'    => "ADD COLUMN updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
			];
			foreach ( $add_cm as $col => $sql ) {
				if ( ! in_array( $col, $existing_cm, true ) ) {
					$wpdb->query( "ALTER TABLE {$contractors} " . $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- nazwy tabel z $wpdb->prefix, wartosci przez $wpdb->prepare().
				}
			}
			// Stary UNIQUE na contractor_nip blokuje rekordy bez NIP (B2C) — usuń
			$idx = $wpdb->get_results( "SHOW INDEX FROM {$contractors} WHERE Key_name = 'contractor_nip'" );
			if ( ! empty( $idx ) ) {
				$wpdb->query( "ALTER TABLE {$contractors} DROP INDEX contractor_nip" );
			}
		}

		// v7 — sync_incoming / sync_outgoing flagi per credential
		$creds_tbl = $wpdb->prefix . 'gksef_credentials';
		$existing_cr = $wpdb->get_col( "SHOW COLUMNS FROM {$creds_tbl}" );
		if ( ! empty( $existing_cr ) ) {
			$existing_cr = array_map( 'strtolower', $existing_cr );
			foreach ( [
				'sync_incoming'         => "ADD COLUMN sync_incoming tinyint(1) NOT NULL DEFAULT 1",
				'sync_outgoing'         => "ADD COLUMN sync_outgoing tinyint(1) NOT NULL DEFAULT 0",
				'last_outgoing_sync_at' => "ADD COLUMN last_outgoing_sync_at datetime DEFAULT NULL",
				// v8 — dane sprzedawcy per kredencjał (obowiązkowe na karcie integracji)
				'seller_name'           => "ADD COLUMN seller_name varchar(255) NOT NULL DEFAULT ''",
				'seller_email'          => "ADD COLUMN seller_email varchar(190) NOT NULL DEFAULT ''",
				'seller_phone'          => "ADD COLUMN seller_phone varchar(40) NOT NULL DEFAULT ''",
				'seller_street'         => "ADD COLUMN seller_street varchar(255) NOT NULL DEFAULT ''",
				'seller_building'       => "ADD COLUMN seller_building varchar(32) NOT NULL DEFAULT ''",
				'seller_postal'         => "ADD COLUMN seller_postal varchar(20) NOT NULL DEFAULT ''",
				'seller_city'           => "ADD COLUMN seller_city varchar(120) NOT NULL DEFAULT ''",
				'seller_country'        => "ADD COLUMN seller_country varchar(3) NOT NULL DEFAULT 'PL'",
			] as $col => $sql ) {
				if ( ! in_array( $col, $existing_cr, true ) ) {
					$wpdb->query( "ALTER TABLE {$creds_tbl} " . $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- nazwy tabel z $wpdb->prefix, wartosci przez $wpdb->prepare().
				}
			}
		}

		// v5 — bank_accounts gdy nie istnieje
		$banks = $wpdb->prefix . 'gksef_bank_accounts';
		$exists_b = $wpdb->get_var( "SHOW TABLES LIKE '{$banks}'" );
		if ( ! $exists_b ) {
			$cc = $wpdb->get_charset_collate();
			$wpdb->query( "CREATE TABLE {$banks} (
				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
				credential_id bigint(20) NOT NULL,
				account_number varchar(64) NOT NULL,
				bank_name varchar(255),
				swift varchar(32),
				currency char(3) DEFAULT 'PLN',
				is_default tinyint(1) DEFAULT 0,
				display_order int(11) DEFAULT 0,
				created_at datetime DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_credential (credential_id)
			) $cc" );
		}

		// Tabela płatności (gdy ktoś jest na starym schemacie i v4 migracja nie odpaliła)
		$payments = $wpdb->prefix . 'gksef_invoice_payments';
		$exists = $wpdb->get_var( "SHOW TABLES LIKE '{$payments}'" );
		if ( ! $exists ) {
			$charset_collate = $wpdb->get_charset_collate();
			$wpdb->query( "CREATE TABLE {$payments} (
				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
				invoice_id bigint(20) NOT NULL,
				amount decimal(15,2) NOT NULL,
				currency char(3) DEFAULT 'PLN',
				payment_date date NOT NULL,
				payment_method varchar(64) DEFAULT '',
				reference varchar(255) DEFAULT '',
				note text,
				created_at datetime DEFAULT CURRENT_TIMESTAMP,
				INDEX idx_invoice (invoice_id),
				INDEX idx_payment_date (payment_date)
			) $charset_collate" );
		}
	}
}
