<?php
/**
 * Duck-typed shop-order replacement for Pay4All Form orders.
 *
 * The TWINT engine (Wallet\WalletRequest / Wallet\WalletPaymentHandler) expects
 * an order-like object with shop-order-shaped getters. Rather than refactor the
 * engine, we wrap a p4all_order CPT with a tight class that exposes exactly the
 * methods the engine touches.
 *
 * Storage: every meta read/write is a direct post-meta operation on the
 * underlying p4all_order post. We mirror shop-order's "batch then save" pattern
 * (update_meta_data → save) so callers can use the same idiom.
 *
 * Scope: the engine never calls payment_complete, update_status, get_items,
 * get_user_id, refunds, etc. on Form orders — those paths are stubbed for
 * safety (no-op or sensible default) but the real status transitions happen
 * through Pay4All Form's own Order::update_payment_status() in the webhook
 * handler.
 */

namespace Twnt4All\Pay4All;

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

final class FormOrderShim
{
    /** @var int */
    private $order_id;

    /** @var \WP_Post|null */
    private $post;

    /** @var array<string,mixed> */
    private $customer;

    /** @var array<string,mixed> */
    private $totals;

    /** @var array<string,mixed> */
    private $delivery;

    /** Pending meta writes queued by update_meta_data, flushed by save(). */
    private $meta_writes = [];

    /** Pending meta deletes queued by delete_meta_data, flushed by save(). */
    private $meta_deletes = [];

    public function __construct( int $order_id )
    {
        $this->order_id = $order_id;
        $this->post     = get_post( $order_id );
        $this->customer = (array) get_post_meta( $order_id, '_p4all_customer', true );
        $this->totals   = (array) get_post_meta( $order_id, '_p4all_totals',   true );
        $this->delivery = (array) get_post_meta( $order_id, '_p4all_delivery', true );
    }

    public function is_loaded() : bool
    {
        return $this->post instanceof \WP_Post && $this->post->post_type === 'p4all_order';
    }

    public static function is_form_order_id( int $order_id ) : bool
    {
        if ( $order_id <= 0 ) { return false; }
        $post = get_post( $order_id );
        return $post instanceof \WP_Post && $post->post_type === 'p4all_order';
    }

    // -----------------------------------------------------------------
    // Identity / numeric properties
    // -----------------------------------------------------------------

    public function get_id() : int
    {
        return (int) $this->order_id;
    }

    public function get_order_number() : string
    {
        return (string) get_post_meta( $this->order_id, '_p4all_order_number', true ) ?: (string) $this->order_id;
    }

    public function get_total() : float
    {
        return (float) ( $this->totals[ 'total' ] ?? 0 );
    }

    public function get_currency() : string
    {
        // Pay4All Shop 1.4.0+ stores `currency` as a snapshot array
        // [ code, symbol, position, decimals, dec_sep, thou_sep ]. Older
        // orders still have `currency` as a plain code string — accept both
        // so a TWINT charge / refund on an upgraded install never sees the
        // string « Array » in place of the ISO code.
        $c = $this->totals[ 'currency' ] ?? 'CHF';
        if ( is_array( $c ) ) { $c = (string) ( $c[ 'code' ] ?? 'CHF' ); }
        return strtoupper( (string) $c );
    }

    public function get_status() : string
    {
        return (string) ( $this->post ? $this->post->post_status : '' );
    }

    // -----------------------------------------------------------------
    // Billing / shipping — mapped from Pay4All Form customer struct
    // -----------------------------------------------------------------

    public function get_billing_first_name() : string { return (string) ( $this->customer[ 'firstname' ] ?? '' ); }
    public function get_billing_last_name()  : string { return (string) ( $this->customer[ 'lastname' ]  ?? '' ); }
    public function get_billing_company()    : string { return (string) ( $this->customer[ 'company' ]   ?? '' ); }
    public function get_billing_address_1()  : string { return (string) ( $this->customer[ 'address' ]   ?? '' ); }
    public function get_billing_address_2()  : string { return ''; }
    public function get_billing_postcode()   : string { return (string) ( $this->customer[ 'zip' ]       ?? '' ); }
    public function get_billing_city()       : string { return (string) ( $this->customer[ 'city' ]      ?? '' ); }
    public function get_billing_country()    : string { return (string) ( $this->customer[ 'country' ]   ?? '' ); }
    public function get_billing_phone()      : string { return (string) ( $this->customer[ 'phone' ]     ?? '' ); }
    public function get_billing_email()      : string { return (string) ( $this->customer[ 'email' ]     ?? '' ); }

    // Shipping reuses billing (Form doesn't separate addresses in V1).
    public function get_shipping_first_name() : string { return $this->get_billing_first_name(); }
    public function get_shipping_last_name()  : string { return $this->get_billing_last_name(); }
    public function get_shipping_company()    : string { return $this->get_billing_company(); }
    public function get_shipping_address_1()  : string { return $this->get_billing_address_1(); }
    public function get_shipping_address_2()  : string { return ''; }
    public function get_shipping_postcode()   : string { return $this->get_billing_postcode(); }
    public function get_shipping_city()       : string { return $this->get_billing_city(); }
    public function get_shipping_country()    : string { return $this->get_billing_country(); }

    public function get_user_id() : int
    {
        return $this->post ? (int) $this->post->post_author : 0;
    }

    // -----------------------------------------------------------------
    // Meta API — mirrors shop-order's batched semantics
    // -----------------------------------------------------------------

    /**
     * @param string $key
     * @param bool   $single
     * @return mixed
     */
    public function get_meta( $key, $single = true )
    {
        if ( array_key_exists( $key, $this->meta_writes ) ) {
            return $this->meta_writes[ $key ];
        }
        if ( array_key_exists( $key, $this->meta_deletes ) ) {
            return '';
        }
        return get_post_meta( $this->order_id, $key, (bool) $single );
    }

    /**
     * @param string $key
     * @param mixed  $value
     */
    public function update_meta_data( $key, $value ) : void
    {
        $this->meta_writes[ (string) $key ] = $value;
        unset( $this->meta_deletes[ (string) $key ] );
    }

    /**
     * @param string $key
     * @param mixed  $value
     */
    public function delete_meta_data( $key, $value = '' ) : void
    {
        $this->meta_deletes[ (string) $key ] = true;
        unset( $this->meta_writes[ (string) $key ] );
    }

    /**
     * Flush queued meta operations. Mirrors shop-order::save().
     */
    public function save() : int
    {
        foreach ( $this->meta_writes as $k => $v ) {
            update_post_meta( $this->order_id, $k, $v );
        }
        foreach ( $this->meta_deletes as $k => $_ ) {
            delete_post_meta( $this->order_id, $k );
        }
        $this->meta_writes  = [];
        $this->meta_deletes = [];
        return $this->order_id;
    }

    // -----------------------------------------------------------------
    // No-ops / safe defaults for engine paths we explicitly do not want
    // to reach for Form orders. They are here to avoid hard fatals if
    // somebody calls them by mistake — the real status transitions are
    // driven by FormWebhookHandler via Pay4All\Form\Domain\Order.
    // -----------------------------------------------------------------

    public function get_items( $type = 'line_item' ) : array { return []; }

    public function get_checkout_payment_url( $on_checkout = false ) : string
    {
        // Used when the engine wants to "send the user back to checkout" after
        // a failed step. For Form orders we route to the form page (best effort
        // via referer stored at submission time) or fall back to home.
        $ref = (string) get_post_meta( $this->order_id, '_p4all_form_referer', true );
        return $ref !== '' ? $ref : home_url( '/' );
    }

    public function get_checkout_order_received_url() : string
    {
        $number = $this->get_order_number();
        $base   = $this->get_checkout_payment_url();
        return add_query_arg( 'p4all_thankyou', rawurlencode( $number ), $base );
    }

    public function get_cancel_order_url() : string
    {
        return $this->get_checkout_payment_url();
    }

    public function get_payment_method() : string
    {
        $pay = (array) get_post_meta( $this->order_id, '_p4all_payment', true );
        return (string) ( $pay[ 'method' ] ?? 'twint_auto' );
    }

    public function add_order_note( $note ) : int
    {
        if ( class_exists( '\\Pay4All\\Form\\Domain\\Order' ) ) {
            return \Pay4All\Form\Domain\Order::add_note( $this->order_id, (string) $note, false );
        }
        return 0;
    }

    public function payment_complete( $transaction_id = '' ) : bool
    {
        // The Form webhook handler updates the payment_status directly. This
        // shim does NOT auto-complete the order here — it just records the
        // transaction id so subsequent reads are coherent.
        if ( $transaction_id !== '' ) {
            update_post_meta( $this->order_id, '_p4all_twint_transaction_id', (string) $transaction_id );
        }
        return true;
    }

    public function update_status( $new_status, $note = '', $manual = false ) : bool
    {
        // Intentional no-op: status mapping is done by FormWebhookHandler so it
        // can apply the user-configured Pay4All Form statuses (not WC ones).
        return true;
    }
}
