<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Integrates PB UAE VAT documents with WooCommerce transactional emails.
 *
 * Automatic customer emails are always sent by WooCommerce. This class only
 * issues eligible documents when necessary and attaches their cached PDFs to
 * the WooCommerce email IDs selected by the administrator.
 */
class PBUAIV_Email_Manager {

    private static ?PBUAIV_Email_Manager $instance = null;

    private bool $processing_attachments = false;

    /** @var array<string,string|null> */
    private array $attachment_cache = [];

    public static function instance() : self {
        if ( null === self::$instance ) self::$instance = new self();
        return self::$instance;
    }

    private function __construct() {
        add_filter( 'woocommerce_email_attachments', [ $this, 'attach_documents' ], 10, 4 );
    }

    /**
     * Attach configured document PDFs to WooCommerce transactional emails.
     *
     * @param array<int,string> $attachments Existing attachment paths.
     * @param string            $email_id    WooCommerce email ID.
     * @param object            $object      Email object, normally WC_Order.
     * @param object|null       $email       WC_Email instance when supplied.
     * @return array<int,string>
     */
    public function attach_documents( array $attachments, string $email_id, $object, $email = null ) : array {
        if ( $this->processing_attachments ) {
            return $attachments;
        }

        // Trust the WC_Email object's ID when available. This is the canonical
        // identifier used by WooCommerce and avoids class-name/key mismatches.
        if ( is_object( $email ) && isset( $email->id ) ) {
            $email_id = (string) $email->id;
        }
        $email_id = PBUAIV_Settings_Schema::normalize_email_id( $email_id );

        if ( ! $object instanceof WC_Order ) {
            return $attachments;
        }

        // Refetch the order because email filters can receive a stale order
        // instance while HPOS metadata has already been written in this request.
        $order = wc_get_order( $object->get_id() );
        if ( ! $order ) {
            return $attachments;
        }

        $this->processing_attachments = true;

        try {
            foreach ( $this->get_attachment_rules() as $doc_type => $allowed_email_ids ) {
                if ( ! in_array( $email_id, $allowed_email_ids, true ) ) {
                    continue;
                }

                $doc = self::factory( $doc_type, $order );
                if ( ! $doc ) {
                    continue;
                }

                // Backstop timing differences between WooCommerce status hooks
                // and email notifications: issue the document here only when
                // the current order status is configured for auto-generation.
                if ( ! $doc->is_issued() && $this->should_auto_issue( $doc_type, $order->get_status() ) ) {
                    $doc->issue();
                }

                if ( ! $doc->is_issued() ) {
                    unset( $doc );
                    continue;
                }

                $cache_key = $order->get_id() . ':' . $doc_type;
                if ( ! array_key_exists( $cache_key, $this->attachment_cache ) ) {
                    $this->attachment_cache[ $cache_key ] = $this->get_or_generate_pdf_path( $doc );
                }

                $pdf_path = $this->attachment_cache[ $cache_key ];
                if ( $pdf_path && is_readable( $pdf_path ) && ! in_array( $pdf_path, $attachments, true ) ) {
                    $attachments[] = $pdf_path;
                }

                unset( $doc );
                if ( function_exists( 'gc_collect_cycles' ) ) {
                    gc_collect_cycles();
                }
            }
        } finally {
            $this->processing_attachments = false;
        }

        return array_values( array_unique( $attachments ) );
    }

    /** @return array<string,array<int,string>> */
    private function get_attachment_rules() : array {
        $rules = [];
        foreach ( PBUAIV_Settings_Schema::attachment_rules() as $type => $rule ) {
            $rules[ $type ] = PBUAIV_Settings_Schema::get_array_option( $rule['option'] );
        }
        return apply_filters( 'pbuaiv_email_attachment_rules', $rules );
    }

    private function should_auto_issue( string $doc_type, string $order_status ) : bool {
        $rules = PBUAIV_Settings_Schema::automation_rules();
        if ( ! isset( $rules[ $doc_type ] ) ) {
            return false;
        }

        $statuses = PBUAIV_Settings_Schema::get_array_option( $rules[ $doc_type ]['option'] );
        return in_array( sanitize_key( $order_status ), $statuses, true );
    }

    private function get_or_generate_pdf_path( PBUAIV_Document $doc ) : ?string {
        $upload_dir = wp_upload_dir();
        if ( ! empty( $upload_dir['error'] ) ) {
            return null;
        }

        $dir = trailingslashit( $upload_dir['basedir'] ) . 'pbuaiv-documents';
        if ( ! wp_mkdir_p( $dir ) && ! is_dir( $dir ) ) {
            return null;
        }

        $htaccess = trailingslashit( $dir ) . '.htaccess';
        if ( ! file_exists( $htaccess ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- static protection file in uploads.
            file_put_contents( $htaccess, "Deny from all\n" );
        }

        $path = trailingslashit( $dir ) . sanitize_file_name( $doc->get_pdf_filename() );
        if ( file_exists( $path ) && filesize( $path ) > 0 ) {
            return $path;
        }

        $pdf = $doc->get_pdf();
        if ( '' === $pdf ) {
            return null;
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- binary PDF in private uploads directory.
        $written = file_put_contents( $path, $pdf );
        unset( $pdf );

        if ( function_exists( 'gc_collect_cycles' ) ) {
            gc_collect_cycles();
        }

        return false !== $written && file_exists( $path ) && filesize( $path ) > 0 ? $path : null;
    }

    /**
     * Send a single document manually from the order admin screen.
     * Automatic status emails do not use this method.
     */
    public static function send_manual( WC_Order $order, string $doc_type ) : bool {
        $doc = self::factory( $doc_type, $order );
        if ( ! $doc || ! $doc->is_issued() || ! is_email( $order->get_billing_email() ) ) return false;

        $data = $doc->get_data();
        $to   = $order->get_billing_email();
        /* translators: 1: document label, 2: order number. */
        $subject  = sprintf( __( 'Your %1$s for Order #%2$s', 'pb-invoicing-system' ), $data['label'], $order->get_order_number() );
        $template = PBUAIV_PLUGIN_DIR . 'templates/email/document-email.php';

        ob_start();
        extract( $data, EXTR_SKIP );
        if ( file_exists( $template ) ) include $template;
        $message = (string) ob_get_clean();

        $manager  = self::instance();
        $pdf_path = $manager->get_or_generate_pdf_path( $doc );
        $sent     = wp_mail(
            $to,
            $subject,
            $message,
            [ 'Content-Type: text/html; charset=UTF-8' ],
            $pdf_path ? [ $pdf_path ] : []
        );

        if ( $sent ) $doc->mark_sent();
        return $sent;
    }

    public static function factory( string $type, WC_Order $order ) : ?PBUAIV_Document {
        $map = [
            'invoice'       => PBUAIV_Invoice::class,
            'receipt'       => PBUAIV_Receipt::class,
            'credit_note'   => PBUAIV_Credit_Note::class,
            'proforma'      => PBUAIV_Proforma::class,
            'packing_slip'  => PBUAIV_Packing_Slip::class,
            'delivery_note' => PBUAIV_Delivery_Note::class,
        ];
        if ( ! isset( $map[ $type ] ) ) return null;
        return new $map[ $type ]( $order );
    }
}
