<?php

namespace KirilKirkov\WpInvoices;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class CreateInvoice
{
    private static $validation_errors = [];

    /**
     * Admin - create invoice
     */
    public function create()
    {
        $success = null;
        $invoice = [];
        $adata = [];
        $preview_after_create = false;

        // if submit form
        if (
            isset( $_SERVER['REQUEST_METHOD'] ) && 
            $_SERVER['REQUEST_METHOD'] === 'POST' &&
            isset($_POST['kkwi_create_invoice_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['kkwi_create_invoice_nonce'])), 'kkwi_create_invoice_action')) {

            // Check if wants to attach woocommerce order
            if(isset($_POST[Config::INPUTS_PREFIX.'attach_to_wc_order']) && (int)$_POST[Config::INPUTS_PREFIX.'attach_to_wc_order'] > 0) {
                $_POST['wc_id'] = isset($_POST[Config::INPUTS_PREFIX.'get_data_wc_order_id']) ? sanitize_text_field(wp_unslash($_POST[Config::INPUTS_PREFIX.'get_data_wc_order_id'])) : 0;
            }

            $_invoice = self::create_invoice($_POST);

            if($_invoice === false) {
                $errors = self::get_validation_errors();
                $success = false;
                $invoice = $this->recursive_sanitize_text_field(wp_unslash($_POST));
            } else {
                $success = true;
                $preview_after_create = $_invoice['id'];

                // When success insert/update, add/update additional data
                if(isset($_POST['id']) && (int)$_POST['id'] > 0 && isset($_POST[Config::INPUTS_PREFIX.'adata'])) { // when update invoice
                    InvoicesAData::update_adata(sanitize_text_field(wp_unslash($_POST[Config::INPUTS_PREFIX.'adata'])), (int)$_POST['id']);
                } else {
                    InvoicesAData::set_adata(sanitize_text_field(wp_unslash($_POST[Config::INPUTS_PREFIX.'adata'])), $_invoice['id']);
                }
            }
        }

        // if edit
        if(isset($_GET['edit'])) {
            $_invoice = Invoices::get_invoice(sanitize_text_field(wp_unslash($_GET['edit'])), true);
            if($_invoice === false) {
                require plugin_dir_path( __FILE__ ) . '../../Views/404.php';
                return;
            }
            if(!count($invoice)) {
                $invoice = $_invoice;
            }
            $adata = InvoicesAData::get_adata(sanitize_text_field(wp_unslash($_GET['edit'])));
        } else {
            $invoice[Config::INPUTS_PREFIX.'invoice_type'] = 'invoice';
            if(isset($_GET['invoice_type'])) {
                $invoice[Config::INPUTS_PREFIX.'invoice_type'] = sanitize_text_field(wp_unslash($_GET['invoice_type']));
            }
        }

        $Config = Config::class;

        // set default options from first ordered key
        $currencies = get_option($Config::INPUTS_PREFIX.'currencies');
        $kirilkirkovwipdf_selected_currency = Config::get_selected_currency();
        if(isset($invoice[Config::INPUTS_PREFIX.'currency'])) {
            $kirilkirkovwipdf_selected_currency = $invoice[Config::INPUTS_PREFIX.'currency'];
        }

        $disabled_js_calc = get_option($Config::INPUTS_PREFIX.'disabled_js_calc');
        
        $next_inv_number = Invoices::get_next_inv_number($invoice[Config::INPUTS_PREFIX.'invoice_type']);
        $dt = new \DateTime();
        $today_date = $dt->format(Config::get_dates_format_type());

        $Woo = new Woo();

        $isValidGetDataWcOrder = false;
        if(isset($_GET[Config::INPUTS_PREFIX.'get_data_wc_order_id']) 
            && trim(sanitize_text_field(wp_unslash($_GET[Config::INPUTS_PREFIX.'get_data_wc_order_id']))) != '') {
            $isValidGetDataWcOrder = $Woo->is_valid_order_id(sanitize_text_field(wp_unslash($_GET[Config::INPUTS_PREFIX.'get_data_wc_order_id'])));

            if($isValidGetDataWcOrder) {
                $origin_invoice_type_class_var = $invoice[Config::INPUTS_PREFIX.'invoice_type'];

                $invoice = $Woo->generate_invoice_for_order($Woo->get_order_object_by_id(sanitize_text_field(wp_unslash($_GET[Config::INPUTS_PREFIX.'get_data_wc_order_id'])))  , true);
                unset($invoice['id']);
                unset($invoice['wc_id']);
                unset($invoice[Config::INPUTS_PREFIX.'invoice_number']);
                unset($invoice[Config::INPUTS_PREFIX.'to_invoice_number']);
                unset($invoice[Config::INPUTS_PREFIX.'from_date']);

                unset($invoice[Config::INPUTS_PREFIX.'invoice_type']);
                $invoice[Config::INPUTS_PREFIX.'invoice_type'] = $origin_invoice_type_class_var;
            }
        }

        require plugin_dir_path( __FILE__ ) . '../../Views/Invoices/Create.php';
    }

    // Recursive sanitize text field
    private function recursive_sanitize_text_field($data) {
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = $this->recursive_sanitize_text_field($value);
            }
            return $data;
        }

        return sanitize_text_field($data);
    }

    /**
     * @return bool
     */
    public static function create_invoice($post)
    {
        if(!isset($_POST['kkwi_create_invoice_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['kkwi_create_invoice_nonce'])), 'kkwi_create_invoice_action')) {
            self::set_validation_error('general', esc_html__('Security check failed. Please try again.', 'kirilkirkov-pdf-invoice-manager'));
            return false;
        }

        $post = array_map('stripslashes_deep', $post);

        // validate
        if(!self::validate_invoice($post)) {
            return false;
        }

        $w = new Invoices();
        $current_user_id = get_current_user_id();
        global $wpdb;

        $edit_id = isset($post['id']) ? (int)$post['id'] : 0;
        $wc_id = isset($post['wc_id']) && (int)$post['wc_id'] > 0 ? (int)$post['wc_id'] : 0;
        $customer_user_id = isset($post[Config::INPUTS_PREFIX.'customer_user_id']) && (int)$post[Config::INPUTS_PREFIX.'customer_user_id'] > 0 ? (int)$post[Config::INPUTS_PREFIX.'customer_user_id'] : 0;

        // if is insert
        if($edit_id < 1) {

            // We need transaction here. No cached queries.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->query('START TRANSACTION');
            $inv_type = trim($post[Config::INPUTS_PREFIX.'invoice_type']);

            if((int)get_option(Config::INPUTS_PREFIX.'disalbe_origin_inv_generator') < 1) {
                $inv_num = Invoices::full_document_number(trim($post[Config::INPUTS_PREFIX.'invoice_number']));
            } else {
                $inv_num = trim($post[Config::INPUTS_PREFIX.'invoice_number']);
            }
            
            $dt = new \DateTime();
            $created_at = $dt->format('Y-m-d H:i:s');

            // Do not need cache for insert. Direct safe query with prepare.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $r = $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO ".esc_sql($w->table_name)." 
                    ( invoice_type, to_invoice_number, from_date, invoice_number, invoice_date, 
                    date_tax_event, due_date, has_maturity, maturity_date, cash_accounting, currency, items_total, 
                    discount, discount_type, vat_percent, vat_sum, no_vat, no_vat_reason, tax_base, 
                    final_total, remarks, payment_method, created_from, compiled, schiffer, created_at, updated_at, wc_id, customer_user_id )
                    VALUES ( %s, %s, %s, %s, %s, %s, %s, %d, %s, %d, %s, %s, %s, %s, %s, %s, %d, %s, 
                    %s, %s, %s, %s, %s, %s, %s, %s, %s, %d, %d )",
                    $inv_type,
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'to_invoice_number']), 0, 200),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'from_date']), 0, 100),
                    $inv_num,
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'invoice_date']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'date_tax_event']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'due_date']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'has_maturity'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'maturity_date']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'cash_accounting'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'currency']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'items_total']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'discount']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'discount_type']), 0, 50),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'vat_percent']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'vat_sum']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'no_vat'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'no_vat_reason']), 0, 255),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'tax_base']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'final_total']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'remarks']), 0, 65535),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'payment_method']), 0, 255),
                    $current_user_id,
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'compiled']), 0, 255),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'schiffer']), 0, 255),
                    $created_at,
                    $created_at,
                    $wc_id,
                    $customer_user_id
                )
            );
            $insert_id = $wpdb->insert_id;

            $client_from_list = (int)$post[Config::INPUTS_PREFIX.'client_from_list'];
            $client = $post[Config::INPUTS_PREFIX.'client'];
            $c = self::set_client($client, $client_from_list, $insert_id);
            $i = self::set_items($post, $insert_id);

            // If wants to save as periodic inv
            $save_periodic = true;
            if(isset($post[Config::INPUTS_PREFIX.'save_periodic_inv']) && (int)$post[Config::INPUTS_PREFIX.'save_periodic_inv'] > 0) {
                $periodicInvoices = new PeriodicInvoices(true);

                // Do not need cache for insert. Direct insert query with prepare.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $save_periodic = $wpdb->query(
                    $wpdb->prepare(
                        "INSERT INTO ".esc_sql($periodicInvoices->table_name)." 
                        ( invoice_id, reissue_type, reissue_data, last_reissue_date, next_reissue_date )
                        VALUES ( %d, %s, %s, %s, %s )",
                        $insert_id,
                        'weekly',
                        null,
                        null,
                        PeriodicInvoices::get_next_reissue_date('weekly', trim($post[Config::INPUTS_PREFIX.'invoice_date']))
                    )
                );
            }

            if($r && $c && $i && $save_periodic) {

                // No need cache. Transaction direct query.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->query('COMMIT');
                
                if($wc_id < 1) {
                    Items::decrease_quantity($post);
                }
            } else {
                // No need cache. Transaction direct query.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->query('ROLLBACK');
                return false;
            }

            return ['id' => $insert_id];
        }
        
        // if is update
        if($edit_id > 0) {
            $dt = new \DateTime();
            $updated_at = $dt->format('Y-m-d H:i:s');
            $old_invoice_items = Invoices::get_invoice_items($edit_id);

            // No need cache for update. Direct safe query with prepare.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $r = $wpdb->query(
                $wpdb->prepare(
                    "UPDATE ".esc_sql($w->table_name)." 
                    SET invoice_type =  %s, to_invoice_number = %s, from_date = %s, invoice_number = %s, 
                    invoice_date = %s, date_tax_event = %s, due_date = %s, has_maturity = %d, maturity_date = %s, 
                    cash_accounting = %d, currency = %s, items_total = %s, discount = %s, 
                    discount_type = %s, vat_percent = %s, vat_sum = %s, no_vat = %d, no_vat_reason = %s, 
                    final_total = %s, remarks = %s, payment_method = %s, tax_base = %s, updated_at = %s, 
                    compiled = %s, schiffer = %s, customer_user_id = %d  
                    WHERE id = %d LIMIT 1",
                    trim($post[Config::INPUTS_PREFIX.'invoice_type']),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'to_invoice_number']), 0, 200),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'from_date']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'invoice_number']), 0, 150),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'invoice_date']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'date_tax_event']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'due_date']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'has_maturity'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'maturity_date']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'cash_accounting'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'currency']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'items_total']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'discount']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'discount_type']), 0, 50),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'vat_percent']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'vat_sum']), 0, 100),
                    (int)$post[Config::INPUTS_PREFIX.'no_vat'],
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'no_vat_reason']), 0, 255),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'final_total']), 0, 100),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'remarks']), 0, 65535),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'payment_method']), 0, 255),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'tax_base']), 0, 100),
                    $updated_at,
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'compiled']), 0, 255),
                    mb_substr(trim($post[Config::INPUTS_PREFIX.'schiffer']), 0, 255),
                    $customer_user_id,
                    $edit_id
                )
            );

            $client_from_list = (int)$post[Config::INPUTS_PREFIX.'client_from_list'];
            $client = $post[Config::INPUTS_PREFIX.'client'];
            $c = self::set_client($client, $client_from_list, $edit_id);
            $i = self::set_items($post, $edit_id);

            // check is periodic inv
            self::onUpdateInvoiceCheckForPeriodicallyOption($edit_id, $post, $wpdb);

            if($r && $c && $i) {

                // No need cache. Transaction direct query.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->query('COMMIT');

                // if all inserts are successful, 
                // update items available quantity if has choosed from lists
                Items::update_quantity($post, $old_invoice_items);
            } else {
                // No need cache. Transaction direct query.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->query('ROLLBACK');
                return false;
            }

            return ['id' => $edit_id];
        }

        return false;
    }

    /**
     * When make update on an invoice, check if is marked or not for periodic and 
     * fix relations between invoices and periodic invoices
     */
    private static function onUpdateInvoiceCheckForPeriodicallyOption($edit_id, $post, $wpdb)
    {
        $periodicInvoices = new PeriodicInvoices(true);

            // We need a fresh result directly from the database (not cached). Using $wpdb->get_var() with prepare() – the standard and safe approach for this operation.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $periodic_inv = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id FROM ".esc_sql($periodicInvoices->table_name)." 
                    WHERE invoice_id = %d LIMIT 1",
                    $edit_id
                )
            );
            if(!isset($post[Config::INPUTS_PREFIX.'save_periodic_inv']) || (int)$post[Config::INPUTS_PREFIX.'save_periodic_inv'] < 1) {
                if($periodic_inv) {

                    // No cache need for delete. Direct safe query with prepare.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->query(
                        $wpdb->prepare(
                            "DELETE FROM ".esc_sql($periodicInvoices->table_name)." 
                            WHERE invoice_id = %d LIMIT 1",
                            $edit_id
                        )
                    );
                }
            } else {
                if(!$periodic_inv) {

                    // No cache need for insert. Direct safe query with prepare.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->query(
                        $wpdb->prepare(
                            "INSERT INTO ".esc_sql($periodicInvoices->table_name)." 
                            ( invoice_id, reissue_type, reissue_data, last_reissue_date, next_reissue_date )
                            VALUES ( %d, %s, %s, %s, %s )",
                            $edit_id,
                            'weekly',
                            null,
                            null,
                            PeriodicInvoices::get_next_reissue_date('weekly', trim($post[Config::INPUTS_PREFIX.'invoice_date']))
                        )
                    );
                }
            }
    }

    /**
     * @return bool
     */
    private static function set_client($post, $client_from_list, $invoice_id)
    {
        $w = new Invoices();
        global $wpdb;

        $invoice_id = (int)$invoice_id;

        // We need a fresh result directly from the database (not cached). Using $wpdb->get_var() – the standard and safe approach for this operation.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $id = $wpdb->get_var( "SELECT invoice_id FROM " . esc_sql($w->table_clients_to_invoices_name) . " WHERE invoice_id = " . esc_sql($invoice_id) . " LIMIT 1");

        $is_individual = isset($post['is_individual']) ? (int)$post['is_individual'] : 0;
        $identification_number = isset($post['identification_number']) ? mb_substr(trim($post['identification_number']), 0, 255) : '';
        $has_vat_registration = isset($post['has_vat_registration']) ? (int)$post['has_vat_registration'] : 0;
        $vat_number = isset($post['vat_number']) ? mb_substr(trim($post['vat_number']), 0, 255) : '';
        $accountable_person = isset($post['accountable_person']) ? mb_substr(trim($post['accountable_person']), 0, 255) : '';
        $city = isset($post['city']) ? mb_substr(trim($post['city']), 0, 255) : '';
        $address = isset($post['address']) ? mb_substr(trim($post['address']), 0, 300) : '';
        $country = isset($post['country']) ? mb_substr(trim($post['country']), 0, 255) : '';
        $beneficiary = isset($post['beneficiary']) ? mb_substr(trim($post['beneficiary']), 0, 255) : '';
        $zip_code = isset($post['zip_code']) ? mb_substr(trim($post['zip_code']), 0, 200) : '';

        if(!$id) {
            $dt = new \DateTime();
            $created_at = $dt->format('Y-m-d H:i:s');

            // No cache need for insert operation. Direct safe query with prepare.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            return $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO ".esc_sql($w->table_clients_to_invoices_name)." 
                    ( invoice_id, client_from_list, client, is_individual, identification_number, has_vat_registration, 
                    vat_number, accountable_person, city, address, country, beneficiary, zip_code, created_at, updated_at )
                    VALUES ( %d, %d, %s, %d, %s, %d, %s, %s, %s, %s, %s, %s, %s, %s, %s )",
                    $invoice_id,
                    $client_from_list,
                    mb_substr(trim($post['client']), 0, 255),
                    $is_individual,
                    $identification_number,
                    $has_vat_registration,
                    $vat_number,
                    $accountable_person,
                    $city,
                    $address,
                    $country,
                    $beneficiary,
                    $zip_code,
                    $created_at,
                    $created_at
                )
            );
        }
        
        // if is update
        if($id && (int)$id > 0) {
            $dt = new \DateTime();
            $updated_at = $dt->format('Y-m-d H:i:s');

            // No cache need for update operation. Direct safe query with prepare.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            return $wpdb->query(
                $wpdb->prepare(
                    "UPDATE ".esc_sql($w->table_clients_to_invoices_name)." 
                    SET client = %s, is_individual = %d, identification_number = %s, has_vat_registration = %d, vat_number = %s, accountable_person = %s, 
                    city = %s, address = %s, country = %s, beneficiary = %s, zip_code = %s, updated_at = %s 
                    WHERE invoice_id = %d LIMIT 1",
                    mb_substr(trim($post['client']), 0, 255),
                    $is_individual,
                    $identification_number,
                    $has_vat_registration,
                    $vat_number,
                    $accountable_person,
                    $city,
                    $address,
                    $country,
                    $beneficiary,
                    $zip_code,
                    $updated_at,
                    $id
                )
            );
        }
        self::set_validation_error(Config::INPUTS_PREFIX.'items', esc_html__('Error client insert', 'kirilkirkov-pdf-invoice-manager'));

        return false;
    }

    private static function set_items($post, $invoice_id)
    {
        if(isset($post[Config::INPUTS_PREFIX.'items']) 
         && is_array($post[Config::INPUTS_PREFIX.'items'])
         && count($post[Config::INPUTS_PREFIX.'items'])) {
            $items = $post[Config::INPUTS_PREFIX.'items'];
            if(isset($items['item_from_list']) && count($items['item_from_list']) > 0) {

                $w = new Invoices();
                global $wpdb;

                ## delete all, then insert new ones (good solution for update and create)

                // Delete. Not need cache for this operation. Using $wpdb->query() with prepare() – the standard and safe approach for this operation. Table name is hardcoded value and escaped.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->query($wpdb->prepare("DELETE FROM " . esc_sql($w->table_items_to_invoices_name) . "  WHERE invoice_id = %d", (int)$invoice_id));
                $success = true;
                foreach($items['item_from_list'] as $k => $item_from_list) {
                    $item_name = isset($items['items_names']) && isset($items['items_names'][$k]) ? mb_substr(trim($items['items_names'][$k]), 0, 255) : '';
                    if(trim($item_name) == '') {
                        self::set_validation_error(Config::INPUTS_PREFIX.'items', esc_html__('Missing item name', 'kirilkirkov-pdf-invoice-manager'));
                        return false;
                    }
                    $items_quantity = isset($items['items_quantities']) && isset($items['items_quantities'][$k]) ? mb_substr(trim($items['items_quantities'][$k]), 0, 100) : '';
                    $items_quantity_type = isset($items['items_quantity_types']) && isset($items['items_quantity_types'][$k]) ? mb_substr(trim($items['items_quantity_types'][$k]), 0, 150) : '';
                    $items_price = isset($items['items_prices']) && isset($items['items_prices'][$k]) ? mb_substr(trim($items['items_prices'][$k]), 0, 100) : '';
                    $items_total = isset($items['items_totals']) && isset($items['items_totals'][$k]) ? mb_substr(trim($items['items_totals'][$k]), 0, 100) : '';

                    ## insert

                    // We need a fresh result directly from the database (not cached). Using $wpdb->query() with prepare() – the standard and safe approach for this operation.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $r = $wpdb->query(
                        $wpdb->prepare(
                            "INSERT INTO ".esc_sql($w->table_items_to_invoices_name)." 
                            ( invoice_id, name, quantity, quantity_type, price, total, item_from_list )
                            VALUES ( %d, %s, %s, %s, %s, %s, %d )",
                            (int)$invoice_id,
                            $item_name,
                            $items_quantity,
                            $items_quantity_type,
                            $items_price,
                            $items_total,
                            (int)$item_from_list
                        )
                    );
                    if(!$r) {
                        self::set_validation_error(Config::INPUTS_PREFIX.'items', esc_html__('Error items inserting', 'kirilkirkov-pdf-invoice-manager'));
                        $success = false;
                    }
                }
                return $success;
            }
         }

         self::set_validation_error(Config::INPUTS_PREFIX.'items', esc_html__('Error items inserting', 'kirilkirkov-pdf-invoice-manager'));
         return false;
    }

    private static function validate_invoice($post)
    {
        // invoice type validation
        if(!isset($post[Config::INPUTS_PREFIX.'invoice_type']) || !in_array($post[Config::INPUTS_PREFIX.'invoice_type'], [
            'pro_forma',
            'invoice',
            'debit_note',
            'credit_note'
        ])) {
            self::set_validation_error(Config::INPUTS_PREFIX.'invoice_type', esc_html__('Please select invoice type', 'kirilkirkov-pdf-invoice-manager'));
        }

        // invoice number validation
        if(!isset($post[Config::INPUTS_PREFIX.'invoice_number'])) {

            // check if is disabled auto generation
            if((int)get_option(Config::INPUTS_PREFIX.'disalbe_origin_inv_generator') < 1) {
                self::set_validation_error(Config::INPUTS_PREFIX.'invoice_number', esc_html__('Please enter invoice number', 'kirilkirkov-pdf-invoice-manager'));
            }

        } else {
            global $wpdb;
            $w = new Invoices();

            // chech if invoice number for this type of document is taken
            $invoice_number = Invoices::full_document_number($post[Config::INPUTS_PREFIX.'invoice_number']);
            $invoice_type = $post[Config::INPUTS_PREFIX.'invoice_type'];
            // if is update
            if(isset($post['id']) && (int)$post['id'] > 0) {
                // We need a fresh result directly from the database (not cached). Using $wpdb->get_var() with prepare() – the standard and safe approach for this operation.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $count = $wpdb->get_var(
                    $wpdb->prepare(
                        "SELECT COUNT(id) as cnt FROM " . esc_sql($w->table_name) . " 
                        WHERE invoice_number = %s AND invoice_type = %s AND id <> %d",
                        $invoice_number,
                        $invoice_type,
                        (int)$post['id']
                    )
                );
            } else {
                // We need a fresh result directly from the database (not cached). Using $wpdb->get_var() with prepare() – the standard and safe approach for this operation.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $count = $wpdb->get_var(
                    $wpdb->prepare(
                        "SELECT COUNT(id) as cnt FROM " . esc_sql($w->table_name) . " 
                        WHERE invoice_number = %s AND invoice_type = %s",
                        $invoice_number,
                        $invoice_type
                    )
                );
            }
            if($count > 0) {
                self::set_validation_error(Config::INPUTS_PREFIX.'invoice_number', esc_html__('Invoice number is taken', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        if(isset($post[Config::INPUTS_PREFIX.'items']) 
         && is_array($post[Config::INPUTS_PREFIX.'items'])
         && count($post[Config::INPUTS_PREFIX.'items'])) {
            $items = $post[Config::INPUTS_PREFIX.'items'];

            if(isset($items['items_quantities']) && count($items['items_quantities']) > 0) {
                foreach($items['items_quantities'] as $quantity) {
                    if(is_numeric($quantity) === false) {
                        self::set_validation_error('items_quantity', esc_html__('Some item quantity is not a number. Please use numbers and dots only', 'kirilkirkov-pdf-invoice-manager'));
                    }
                }
            }

            if(isset($items['items_prices']) && count($items['items_prices']) > 0) {
                foreach($items['items_prices'] as $price) {
                    if(is_numeric($price) === false) {
                        self::set_validation_error('items_price', esc_html__('Some item price is not a number. Please use numbers and dots only', 'kirilkirkov-pdf-invoice-manager'));
                    }
                }
            }
        }

        if(!isset($post[Config::INPUTS_PREFIX.'discount']) || (trim($post[Config::INPUTS_PREFIX.'discount']) !== '' && !is_numeric($post[Config::INPUTS_PREFIX.'discount']))) {
            self::set_validation_error(Config::INPUTS_PREFIX.'discount', esc_html__('Discount is not valid. Please use numbers and dots only', 'kirilkirkov-pdf-invoice-manager'));
        }

        if(!isset($post[Config::INPUTS_PREFIX.'vat_percent']) || (trim($post[Config::INPUTS_PREFIX.'vat_percent']) !== '' && !is_numeric($post[Config::INPUTS_PREFIX.'vat_percent']))) {
            self::set_validation_error(Config::INPUTS_PREFIX.'vat_percent', esc_html__('VAT percent is not valid. Please use numbers and dots only', 'kirilkirkov-pdf-invoice-manager'));
        }

        if(isset($post[Config::INPUTS_PREFIX.'invoice_date']) && trim($post[Config::INPUTS_PREFIX.'invoice_date']) !== '') {
            $date = \DateTime::createFromFormat(Config::get_dates_format_type(), $post[Config::INPUTS_PREFIX.'invoice_date']);
            if($date === false) {
                self::set_validation_error(Config::INPUTS_PREFIX.'invoice_date', esc_html__('Date is invalid format', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        if(isset($post[Config::INPUTS_PREFIX.'date_tax_event']) && trim($post[Config::INPUTS_PREFIX.'date_tax_event']) !== '') {
            $date = \DateTime::createFromFormat(Config::get_dates_format_type(), $post[Config::INPUTS_PREFIX.'date_tax_event']);
            if($date === false) {
                self::set_validation_error(Config::INPUTS_PREFIX.'date_tax_event', esc_html__('Date is invalid format', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        if(isset($post[Config::INPUTS_PREFIX.'due_date']) && trim($post[Config::INPUTS_PREFIX.'due_date']) !== '') {
            $date = \DateTime::createFromFormat(Config::get_dates_format_type(), $post[Config::INPUTS_PREFIX.'due_date']);
            if($date === false) {
                self::set_validation_error(Config::INPUTS_PREFIX.'due_date', esc_html__('Due Date is invalid format', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        if(isset($post[Config::INPUTS_PREFIX.'maturity_date']) && trim($post[Config::INPUTS_PREFIX.'maturity_date']) !== '') {
            $date = \DateTime::createFromFormat(Config::get_dates_format_type(), $post[Config::INPUTS_PREFIX.'maturity_date']);
            if($date === false) {
                self::set_validation_error(Config::INPUTS_PREFIX.'maturity_date', esc_html__('Maturity date is invalid format', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        if(isset($post[Config::INPUTS_PREFIX.'from_date']) && trim($post[Config::INPUTS_PREFIX.'from_date']) !== '' && 
        in_array($post[Config::INPUTS_PREFIX.'invoice_type'], [
            'debit_note',
            'credit_note',
        ])) {
            $date = \DateTime::createFromFormat(Config::get_dates_format_type(), $post[Config::INPUTS_PREFIX.'from_date']);
            if($date === false) {
                self::set_validation_error(Config::INPUTS_PREFIX.'from_date', esc_html__('Date is invalid format', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        // Client
        if(!isset($post[Config::INPUTS_PREFIX.'client_from_list'])) {
            self::set_validation_error(Config::INPUTS_PREFIX.'client_from_list', esc_html__('Missing value is client from list', 'kirilkirkov-pdf-invoice-manager'));
        }
        if(!isset($post[Config::INPUTS_PREFIX.'client']) || !is_array($post[Config::INPUTS_PREFIX.'client']) || count($post[Config::INPUTS_PREFIX.'client']) < 1) {
            self::set_validation_error(Config::INPUTS_PREFIX.'client', esc_html__('Client is missing', 'kirilkirkov-pdf-invoice-manager'));
        } else {
            if(!isset($post[Config::INPUTS_PREFIX.'client']['client'])) {
                self::set_validation_error(Config::INPUTS_PREFIX.'client', esc_html__('Client name is missing', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        // If has wc_id, check has no other invoices with this wc_id from same type
        if(isset($post['wc_id']) && (int)$post['wc_id'] > 0) {
            global $wpdb;
            $w = new Invoices();

            // We need a fresh result directly from the database (not cached). Using $wpdb->get_var() with prepare() – the standard and safe approach for this operation.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $count = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(id) as cnt FROM " . esc_sql($w->table_name) . " 
                    WHERE wc_id = %d AND invoice_type = %s",
                    (int)$post['wc_id'],
                    $post[Config::INPUTS_PREFIX.'invoice_type']
                )
            );
            if($count > 0) {
                self::set_validation_error('wc_id', esc_html__('This order is already attached to an invoice. Cannot attach invoices of the same type to the same order.', 'kirilkirkov-pdf-invoice-manager'));
            }
        }

        // Check if attach wc order is valid
        if(isset($post[Config::INPUTS_PREFIX.'attach_to_wc_order']) 
            && (int)$post[Config::INPUTS_PREFIX.'attach_to_wc_order'] > 0) {
            
                if(!isset($post[Config::INPUTS_PREFIX.'get_data_wc_order_id']) || (int)$post[Config::INPUTS_PREFIX.'get_data_wc_order_id'] < 1) {
                    self::set_validation_error('wc_id', esc_html__('Please select valid order id.', 'kirilkirkov-pdf-invoice-manager'));
                }
        }

        return (bool)!count(self::get_validation_errors());
    }

    /**
     * Set error to validation errors
     */
    private static function set_validation_error($key, $err)
    {
        self::$validation_errors[$key] = $err;
    }

    /**
     * @param bool $remove_prefix - remove input prefixes. Mostly used trought Rest Api
     * 
     * @return array
     */
    public static function get_validation_errors($remove_prefix = false)
    {
        if($remove_prefix === true) {
            return array_map(function($key, $value) {
                return [str_replace(Config::INPUTS_PREFIX, '', $key) => $value];
            }, array_keys(self::$validation_errors), self::$validation_errors);
        }

        return self::$validation_errors;
    }
}