<?php
/**
 * Bulk Print Service
 *
 * Handles bulk printing of shipment documents with PDF merging.
 *
 * @package    NovaPostForWoocommerce
 * @subpackage Services
 */

namespace NovaPostForWoocommerce\Services;

use NovaPostForWoocommerce\Database\Repositories\OrderRepository;
use NovaPostForWoocommerce\Logger\Logger;
use NovaPostForWoocommerce\Exceptions\ShipmentException;

/**
 * Bulk Print Service
 *
 * Provides functionality for bulk document printing.
 */
class BulkPrintService {

    /**
     * Maximum number of orders per bulk print request.
     */
    const MAX_ORDERS = 100;

    /**
     * Maximum number of documents per Nova Post API request.
     */
    const API_BATCH_SIZE = 15;

    /**
     * PDF Merger instance.
     *
     * @var PdfMerger
     */
    private $pdf_merger;

    /**
     * Constructor.
     */
    public function __construct() {
        $this->pdf_merger = new PdfMerger();
    }

    /**
     * Print documents for multiple orders and merge into single PDF.
     *
     * @param array  $order_ids Array of WooCommerce order IDs.
     * @param string $type      Document type (marking, invoice, international).
     * @return array {
     *     @type string $pdf_data Base64 encoded merged PDF.
     *     @type array  $stats    Statistics about the operation.
     * }
     * @throws ShipmentException If printing fails.
     */
    public function print_bulk_documents( array $order_ids, string $type ): array {
        // Validate input.
        $this->validate_inputs( $order_ids, $type );

        // Get tracking numbers from orders.
        $tracking_data = $this->get_tracking_numbers( $order_ids );

        if ( empty( $tracking_data['numbers'] ) ) {
            throw new ShipmentException(
                esc_html__( 'No shipments found for selected orders', 'nova-post-for-woocommerce' ),
                404
            );
        }

        // Fetch PDFs with pre-validation and batch processing.
        $fetch_result = $this->fetch_pdfs_validated( $tracking_data, $type );

        // Check if we have any successful PDFs.
        if ( empty( $fetch_result['pdf_binaries'] ) ) {
            throw new ShipmentException(
                esc_html__( 'Failed to generate any documents. All tracking numbers are invalid.', 'nova-post-for-woocommerce' ),
                500
            );
        }

        // Merge all successful PDFs.
        $merged_pdf = $this->pdf_merger->merge( $fetch_result['pdf_binaries'] );

        // Calculate successful count: found_shipments - failed - skipped.
        $successful_count = count( $tracking_data['numbers'] ) - count( $fetch_result['failed'] );

        return array(
            'pdf_data' => base64_encode( $merged_pdf ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
            'numbers'  => $tracking_data['numbers'] ?? array(),
            'summary'  => array(
                'total_orders'    => count( $order_ids ),
                'found_shipments' => count( $tracking_data['numbers'] ),
                'skipped'         => count( $tracking_data['skipped'] ),
                'failed'          => count( $fetch_result['failed'] ),
                'successful'      => $successful_count,
                'skipped_orders'  => $tracking_data['skipped'],
                'failed_orders'   => $fetch_result['failed'],
            ),
        );
    }

    /**
     * Get tracking numbers from orders.
     *
     * @param array $order_ids Array of order IDs.
     * @return array {
     *     @type array $numbers   Array of tracking numbers.
     *     @type array $skipped   Array of skipped order IDs.
     *     @type array $order_map Map of tracking_number => order_id.
     * }
     */
    public function get_tracking_numbers( array $order_ids ): array {
        $tracking_numbers = array();
        $skipped          = array();
        $order_map        = array();

        foreach ( $order_ids as $order_id ) {
            try {
                $order = OrderRepository::get_order_or_fail( $order_id );

                $tracking_number = $order->get_meta( '_nova_post_shipment_number' );

                if ( $tracking_number ) {
                    $tracking_numbers[]            = $tracking_number;
                    $order_map[ $tracking_number ] = $order_id;
                } else {
                    $skipped[] = $order_id;
                }
            } catch ( \Exception $e ) {
                $skipped[] = $order_id;
            }
        }

        return array(
            'numbers'   => $tracking_numbers,
            'skipped'   => $skipped,
            'order_map' => $order_map,
        );
    }

    /**
     * Fetch PDFs from Nova Post API with pre-validation and batch processing.
     *
     * @param array  $tracking_data Array with 'numbers' and 'order_map'.
     * @param string $type          Document type.
     * @return array {
     *     @type array $pdf_binaries Array of PDF binary data.
     *     @type array $failed       Array of failed tracking numbers with errors.
     * }
     */
    private function fetch_pdfs_validated( array $tracking_data, string $type ): array {
        $pdf_binaries = array();
        $failed       = array();

        // 1. Pre-validate tracking numbers with pagination support.
        $found_numbers = array();
        try {
            // API has limit of 100, so we need to paginate if we have more tracking numbers.
            $all_numbers = $tracking_data['numbers'];
            $chunks      = array_chunk( $all_numbers, 100 );

            foreach ( $chunks as $chunk ) {
                $validation_response = ShipmentApiService::get_shipments(
                    array(
                        'numbers' => $chunk,
                        'limit'   => 100,
                    )
                );

                if ( is_array( $validation_response['items'] ) ) {
                    foreach ( $validation_response['items'] as $shipment ) {
                        $found_numbers[] = $shipment['number'];
                    }
                }
            }

            // Identify valid and missing numbers.
            $valid_numbers = array();
            foreach ( $tracking_data['numbers'] as $requested_number ) {
                if ( in_array( $requested_number, $found_numbers, true ) ) {
                    $valid_numbers[] = $requested_number;
                } else {
                    $order_id   = $tracking_data['order_map'][ $requested_number ] ?? null;
                    $error_data = array(
                        'tracking_number' => $requested_number,
                        'order_id'        => $order_id,
                        'error'           => __( 'Shipment not found in Nova Post', 'nova-post-for-woocommerce' ),
                    );
                    $failed[]   = $error_data;
                    Logger::write_log( \WC_Log_Levels::ERROR, 'Bulk print validation failed', $error_data );
                }
            }
        } catch ( \Exception $e ) {
            // If validation fails entirely, mark all as failed (or fallback to individual? No, fail safe).
            foreach ( $tracking_data['numbers'] as $number ) {
                $order_id = $tracking_data['order_map'][ $number ] ?? null;
                $failed[] = array(
                    'tracking_number' => $number,
                    'order_id'        => $order_id,
                    'error'           => __( 'Validation failed: ', 'nova-post-for-woocommerce' ) . $e->getMessage(),
                );
            }

            Logger::write_log(
                \WC_Log_Levels::ERROR,
                'Bulk print validation failed',
                array(
                    'error'   => $e->getMessage(),
                    'numbers' => $tracking_data['numbers'],
                )
            );
            return array(
                'pdf_binaries' => $pdf_binaries,
                'failed'       => $failed,
            );
        }

        // 2. Fetch PDFs for valid numbers in batches.
        if ( ! empty( $valid_numbers ) ) {
            $batches = array_chunk( $valid_numbers, self::API_BATCH_SIZE );

            foreach ( $batches as $batch_numbers ) {
                $params = array(
                    'type'    => $type,
                    'numbers' => $batch_numbers,
                );

                try {
                    $api_response = ShipmentApiService::get_shipment_print( $params );

                    if ( ! empty( $api_response ) ) {
                        $pdf_binaries[] = $api_response;
                    }
                } catch ( \Exception $e ) {
                    // If batch fails, mark all documents in this batch as failed.
                    $error_msg = $e->getMessage();
                    if ( $e instanceof ShipmentException ) {
                        $data = $e->get_data();
                        if ( ! empty( $data['errors'] ) ) {
                            $error_msg .= ': ' . $this->format_api_error( $data['errors'] );
                        }
                    }

                    foreach ( $batch_numbers as $number ) {
                        $order_id = $tracking_data['order_map'][ $number ] ?? null;
                        $failed[] = array(
                            'tracking_number' => $number,
                            'order_id'        => $order_id,
                            'error'           => $error_msg,
                        );
                    }

                    Logger::write_log(
                        \WC_Log_Levels::ERROR,
                        'Bulk print batch failed',
                        array(
                            'error'   => $error_msg,
                            'numbers' => $batch_numbers,
                        )
                    );
                }
            }
        }

        return array(
            'pdf_binaries' => $pdf_binaries,
            'failed'       => $failed,
        );
    }

    /**
     * Format API error response into readable message.
     *
     * @param array|string $errors Error data from API.
     * @return string Formatted error message.
     */
    private function format_api_error( $errors ): string {
        if ( is_string( $errors ) ) {
            return $errors;
        }

        $error_msg = esc_html__( 'Unknown error', 'nova-post-for-woocommerce' );

        if ( is_array( $errors ) ) {
            return implode( ', ', $errors ?? array( $error_msg ) );
        }

        return $error_msg;
    }

    /**
     * Validate request input.
     *
     * @param array  $order_ids Order IDs.
     * @param string $type      Document type.
     * @throws ShipmentException If validation fails.
     */
    private function validate_inputs( array $order_ids, string $type ) {
        // Validate order IDs.
        if ( empty( $order_ids ) ) {
            throw new ShipmentException(
                esc_html__( 'Order IDs must be a non-empty array', 'nova-post-for-woocommerce' ),
                422
            );
        }

        if ( count( $order_ids ) > self::MAX_ORDERS ) {
            throw new ShipmentException(
                sprintf(
                    /* translators: %d: maximum number of orders */
                    esc_html__( 'Maximum %d orders allowed for bulk printing', 'nova-post-for-woocommerce' ),
                    self::MAX_ORDERS // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                ),
                422
            );
        }

        // Validate document type.
        $allowed_types = array( 'marking', 'invoice', 'international' );
        if ( ! in_array( $type, $allowed_types, true ) ) {
            throw new ShipmentException(
                sprintf(
                    /* translators: %s: allowed types */
                    esc_html__( 'Invalid document type. Allowed types: %s', 'nova-post-for-woocommerce' ),
                    esc_html( implode( ', ', $allowed_types ) )
                ),
                422
            );
        }
    }
}
