<?php

if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class BSPP_Stripe_Webhook {
    public function handle() {
        $settings = new BSPP_Settings_Repository();
        $webhook_secret = $settings->get( 'stripe_webhook_secret', '' );
        if ( empty( $webhook_secret ) ) {
            http_response_code( 400 );
            exit;
        }
        $payload = file_get_contents( 'php://input' );
        if ( empty( $payload ) ) {
            http_response_code( 400 );
            exit;
        }
        $sig_header = ( isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? (string) wp_unslash( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) : '' );
        if ( !$this->verify_signature( $payload, $sig_header, $webhook_secret ) ) {
            http_response_code( 400 );
            exit;
        }
        $event = json_decode( $payload, true );
        if ( !is_array( $event ) || empty( $event['id'] ) || empty( $event['type'] ) ) {
            http_response_code( 400 );
            exit;
        }
        $stripe_event_id = (string) $event['id'];
        $stripe_event_type = (string) $event['type'];
        $webhook_repo = new BSPP_Webhook_Event_Repository();
        if ( $webhook_repo->has_processed( $stripe_event_id ) ) {
            http_response_code( 200 );
            exit;
        }
        $webhook_repo->record_event( $stripe_event_id, $stripe_event_type );
        $this->handle_event( $event );
        http_response_code( 200 );
        exit;
    }

    private function handle_event( array $event ) {
        $event_type = $event['type'];
        if ( $event_type === 'checkout.session.completed' ) {
            $session = $event['data']['object'] ?? [];
            if ( is_array( $session ) && !empty( $session['id'] ) ) {
                self::fulfill_checkout_session_completed( $session );
            }
        }
        if ( $event_type === 'customer.subscription.updated' || $event_type === 'customer.subscription.deleted' ) {
            $sub = $event['data']['object'] ?? [];
            if ( is_array( $sub ) && !empty( $sub['id'] ) ) {
                self::sync_membership_from_stripe_subscription( $sub );
            }
        }
    }

    /**
     * Idempotent: update purchase, attach user, grant entitlement or membership.
     * Shared by webhook and browser return URL (local dev / slow webhooks).
     */
    public static function fulfill_checkout_session_completed( array $session ) {
        $metadata = ( is_array( $session['metadata'] ?? null ) ? $session['metadata'] : [] );
        $metadata_user_id = ( isset( $metadata['user_id'] ) ? (int) $metadata['user_id'] : 0 );
        $plan = ( isset( $metadata['plan'] ) ? sanitize_key( $metadata['plan'] ) : 'month' );
        $purchase_type = ( isset( $metadata['purchase_type'] ) ? sanitize_key( $metadata['purchase_type'] ) : 'subscription' );
        $post_id = ( isset( $metadata['post_id'] ) ? (int) $metadata['post_id'] : 0 );
        $stripe_session_id = ( isset( $session['id'] ) ? (string) $session['id'] : '' );
        $customer_email = self::extract_session_customer_email( $session );
        $resolved_user_id = self::resolve_checkout_user_id( $metadata_user_id, $customer_email );
        $session_mode = ( isset( $session['mode'] ) ? (string) $session['mode'] : '' );
        if ( $resolved_user_id <= 0 && $session_mode === 'subscription' && $purchase_type === 'subscription' && $customer_email !== '' ) {
            $resolved_user_id = self::ensure_wp_user_for_subscription_email( $customer_email );
        }
        $purchase_repo = new BSPP_Purchase_Repository();
        if ( !empty( $stripe_session_id ) ) {
            $purchase_repo->update_status_by_session_id( $stripe_session_id, 'succeeded' );
            if ( $resolved_user_id > 0 ) {
                $purchase_repo->update_user_and_email_by_session_id( $stripe_session_id, $resolved_user_id, ( $customer_email !== '' ? $customer_email : null ) );
            }
            $mode = ( isset( $session['mode'] ) ? (string) $session['mode'] : '' );
            $pi = self::stripe_id_from_field( $session['payment_intent'] ?? '' );
            if ( $mode === 'payment' && $pi !== '' && strpos( $pi, 'pi_' ) === 0 ) {
                $purchase_repo->update_payment_intent_by_session_id( $stripe_session_id, $pi );
            }
            if ( $mode === 'subscription' ) {
                $inv = self::stripe_id_from_field( $session['invoice'] ?? '' );
                if ( $inv !== '' && strpos( $inv, 'in_' ) === 0 ) {
                    $purchase_repo->update_invoice_by_session_id( $stripe_session_id, $inv );
                }
            }
        }
        $user_id = $resolved_user_id;
        $pro_purchase_fulfilled = false;
        if ( $pro_purchase_fulfilled ) {
            // Tip / article unlock granted above.
        } elseif ( $user_id > 0 ) {
            $membership_repo = new BSPP_Membership_Repository();
            if ( !$membership_repo->is_active( $user_id ) ) {
                $membership_repo->create(
                    $user_id,
                    $plan,
                    'active',
                    null
                );
            }
            $cid = self::stripe_id_from_field( $session['customer'] ?? '' );
            $sid = self::stripe_id_from_field( $session['subscription'] ?? '' );
            if ( $cid !== '' || $sid !== '' ) {
                $membership_repo->update_stripe_ids_for_active_user( $user_id, $cid, $sid );
            }
            if ( $stripe_session_id !== '' && $session_mode === 'subscription' && $purchase_type === 'subscription' && $metadata_user_id === 0 && $customer_email !== '' && class_exists( 'BSPP_Magic_Login' ) ) {
                $welcome_key = 'bobasoftware_pp_sub_welcome_magic_' . md5( $stripe_session_id );
                if ( !get_transient( $welcome_key ) ) {
                    if ( BSPP_Magic_Login::send_post_subscription_welcome_magic_link( $user_id, $post_id ) ) {
                        set_transient( $welcome_key, 1, 30 * DAY_IN_SECONDS );
                    }
                }
            }
        }
    }

    /**
     * Normalize Stripe API fields that may be an id string or an expanded object.
     */
    private static function stripe_id_from_field( $value ) {
        if ( is_string( $value ) && $value !== '' ) {
            return $value;
        }
        if ( is_array( $value ) && !empty( $value['id'] ) && is_string( $value['id'] ) ) {
            return $value['id'];
        }
        return '';
    }

    /**
     * Keep local membership row aligned with Stripe subscription lifecycle (cancel at period end, final cancel).
     */
    private static function sync_membership_from_stripe_subscription( array $sub ) {
        $sub_id = ( isset( $sub['id'] ) ? (string) $sub['id'] : '' );
        if ( $sub_id === '' || strpos( $sub_id, 'sub_' ) !== 0 ) {
            return;
        }
        $repo = new BSPP_Membership_Repository();
        $row = $repo->get_by_stripe_subscription_id( $sub_id );
        if ( !$row || empty( $row->id ) ) {
            return;
        }
        $membership_id = (int) $row->id;
        $membership_user_id = ( isset( $row->user_id ) ? (int) $row->user_id : 0 );
        $stripe_status = ( isset( $sub['status'] ) ? sanitize_key( (string) $sub['status'] ) : '' );
        $cancel_at_end = !empty( $sub['cancel_at_period_end'] );
        $period_end = null;
        if ( !empty( $sub['current_period_end'] ) && is_numeric( $sub['current_period_end'] ) ) {
            $period_end = gmdate( 'Y-m-d H:i:s', (int) $sub['current_period_end'] );
        }
        $purchase_repo = new BSPP_Purchase_Repository();
        if ( $membership_user_id > 0 && $purchase_repo->user_has_refunded_subscription( $membership_user_id ) ) {
            $repo->update_status_and_period(
                $membership_id,
                'canceled',
                $period_end,
                0
            );
            return;
        }
        $terminal = ['canceled', 'incomplete_expired'];
        if ( in_array( $stripe_status, $terminal, true ) ) {
            $repo->update_status_and_period(
                $membership_id,
                'canceled',
                $period_end,
                0
            );
            return;
        }
        $known = [
            'active'     => 'active',
            'trialing'   => 'trialing',
            'past_due'   => 'past_due',
            'unpaid'     => 'past_due',
            'paused'     => 'active',
            'incomplete' => 'incomplete',
        ];
        $local_status = ( isset( $known[$stripe_status] ) ? $known[$stripe_status] : 'active' );
        $repo->update_status_and_period(
            $membership_id,
            $local_status,
            $period_end,
            ( $cancel_at_end ? 1 : 0 )
        );
    }

    /**
     * Customer email from Checkout Session (metadata user_id may be missing in edge cases).
     */
    private static function extract_session_customer_email( array $session ) {
        $email = '';
        if ( !empty( $session['customer_details'] ) && is_array( $session['customer_details'] ) ) {
            $details = $session['customer_details'];
            if ( !empty( $details['email'] ) && is_string( $details['email'] ) ) {
                $email = sanitize_email( $details['email'] );
            }
        }
        if ( $email === '' && !empty( $session['customer_email'] ) && is_string( $session['customer_email'] ) ) {
            $email = sanitize_email( $session['customer_email'] );
        }
        return $email;
    }

    /**
     * Prefer checkout metadata user; fall back to WP user matched by Stripe customer email.
     */
    private static function resolve_checkout_user_id( $metadata_user_id, $customer_email ) {
        $metadata_user_id = (int) $metadata_user_id;
        if ( $metadata_user_id > 0 && get_user_by( 'id', $metadata_user_id ) ) {
            return $metadata_user_id;
        }
        if ( $customer_email === '' ) {
            return 0;
        }
        $user = get_user_by( 'email', $customer_email );
        return ( $user ? (int) $user->ID : 0 );
    }

    /**
     * After a subscription Checkout completes, attach membership to a WP account for the payer email.
     * Mirrors magic-login account creation when the reader was not logged in at checkout.
     */
    private static function ensure_wp_user_for_subscription_email( $customer_email ) {
        $email = sanitize_email( (string) $customer_email );
        if ( $email === '' || !is_email( $email ) ) {
            return 0;
        }
        $existing = get_user_by( 'email', $email );
        if ( $existing && !empty( $existing->ID ) ) {
            return (int) $existing->ID;
        }
        $username = sanitize_user( current( explode( '@', $email ) ), true );
        if ( $username === '' ) {
            $username = 'reader_' . wp_generate_password( 8, false, false );
        }
        if ( username_exists( $username ) ) {
            $username .= '_' . wp_generate_password( 4, false, false );
        }
        $new_id = wp_create_user( $username, wp_generate_password( 24, true, true ), $email );
        if ( is_wp_error( $new_id ) ) {
            return 0;
        }
        return (int) $new_id;
    }

    private function verify_signature( $payload, $sig_header, $secret ) {
        if ( empty( $sig_header ) || empty( $payload ) || empty( $secret ) ) {
            return false;
        }
        // Stripe signature header format: t=timestamp,v1=signature,v1=signature2...
        $items = explode( ',', $sig_header );
        $timestamp = null;
        $signatures = [];
        foreach ( $items as $item ) {
            $item = trim( $item );
            if ( $item === '' ) {
                continue;
            }
            $parts = explode( '=', $item, 2 );
            if ( count( $parts ) !== 2 ) {
                continue;
            }
            [$k, $v] = $parts;
            $k = trim( $k );
            $v = trim( $v );
            if ( $k === 't' ) {
                $timestamp = $v;
            } elseif ( $k === 'v1' ) {
                $signatures[] = $v;
            }
        }
        if ( $timestamp === null || empty( $signatures ) ) {
            return false;
        }
        $signed_payload = $timestamp . '.' . $payload;
        $expected = hash_hmac(
            'sha256',
            $signed_payload,
            $secret,
            false
        );
        foreach ( $signatures as $sig ) {
            if ( hash_equals( $expected, $sig ) ) {
                return true;
            }
        }
        return false;
    }

}
