<?php
/**
 * Stripe Webhook Handler
 *
 * @package PackshotPhotoAI
 */

namespace PackshotPhotoAI\API;

use PackshotPhotoAI\Core\CreditManager;
use PackshotPhotoAI\Core\Logger;
use WP_REST_Request;
use WP_REST_Response;

defined('ABSPATH') || exit;

/**
 * Handles incoming Stripe webhooks to sync credits
 */
class WebhookHandler {

    /**
     * REST API Namespace
     */
    private const NAMESPACE = 'packshotphoto/v1';

    /**
     * Route base
     */
    private const ROUTE = 'webhook';

    /**
     * Register REST API route for Stripe Webhooks
     */
    public function register_routes(): void {
        register_rest_route(self::NAMESPACE, '/' . self::ROUTE, [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_webhook'],
            'permission_callback' => '__return_true', // Validation happens inside via signature
        ]);
    }

    /**
     * Handle incoming webhook request
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function handle_webhook(WP_REST_Request $request): WP_REST_Response {
        $payload   = $request->get_body();
        $sig_header = $request->get_header('stripe_signature');
        
        $encrypted_secret = get_option('packshotphoto_stripe_webhook_secret', '');
        $secret = \PackshotPhotoAI\Core\Encryption::decrypt($encrypted_secret);

        Logger::debug('Stripe Webhook: Received request', ['payload_preview' => substr($payload, 0, 100)]);

        // 1. Enforce signature verification — secret MUST be configured
        if (empty($secret)) {
            Logger::error('Stripe Webhook: Secret is not configured. Webhook rejected for security.');
            return new WP_REST_Response(['message' => 'Webhook secret not configured'], 500);
        }

        if (!$this->verify_signature($payload, $sig_header, $secret)) {
            Logger::error('Stripe Webhook: Signature verification failed');
            return new WP_REST_Response(['message' => 'Invalid signature'], 400);
        }

        $event = json_decode($payload, true);
        if (!$event || !isset($event['type'])) {
            Logger::error('Stripe Webhook: Invalid JSON payload or missing type', ['payload' => substr($payload, 0, 200)]);
            return new WP_REST_Response(['message' => 'Invalid payload'], 400);
        }

        $event_type = $event['type'];
        $object     = $event['data']['object'] ?? [];

        Logger::info('Stripe Webhook: Processing event', ['type' => $event_type, 'id' => $event['id']]);

        switch ($event_type) {
            case 'checkout.session.completed':
                $this->handle_checkout_completed($object);
                break;

            case 'charge.refunded':
                $this->handle_refund($object);
                break;

            default:
                Logger::debug('Stripe Webhook: Unhandled event type', ['type' => $event_type]);
                break;
        }

        return new WP_REST_Response(['success' => true], 200);
    }

    /**
     * Verify Stripe webhook signature
     *
     * @param string $payload
     * @param string $sig_header
     * @param string $secret
     * @return bool
     */
    private function verify_signature(string $payload, ?string $sig_header, string $secret): bool {
        if (empty($sig_header)) {
            return false;
        }

        // Parse signature header (format: t=TIMESTAMP,v1=HASH)
        $parts = explode(',', $sig_header);
        $t = '';
        $v1 = '';

        foreach ($parts as $part) {
            $kv = explode('=', $part, 2);
            if (count($kv) !== 2) continue;
            if ($kv[0] === 't')  $t  = $kv[1];
            if ($kv[0] === 'v1') $v1 = $kv[1];
        }

        if (empty($t) || empty($v1)) {
            return false;
        }

        // Verify timestamp (prevent replay attacks — default 5 minutes)
        if (abs(time() - (int)$t) > 300) {
            Logger::error('Stripe Webhook: Timestamp too old', ['received' => $t, 'current' => time()]);
            return false;
        }

        // Compute HMAC
        $signed_payload = $t . '.' . $payload;
        $expected_v1    = hash_hmac('sha256', $signed_payload, $secret);

        return hash_equals($expected_v1, $v1);
    }

    /**
     * Handle checkout.session.completed
     *
     * @param array $session
     */
    private function handle_checkout_completed(array $session): void {
        $credits = 0;

        // 1. Try to get credits from metadata
        if (isset($session['metadata']['credits'])) {
            $credits = (int)$session['metadata']['credits'];
        }

        // 2. Fallback to amount mapping if metadata is missing
        if ($credits <= 0) {
            $amount = $session['amount_total'] ?? 0;
            $currency = strtolower($session['currency'] ?? 'usd');

            $credits = $this->map_amount_to_credits($amount, $currency);
        }

        if ($credits > 0) {
            $customer_email = $session['customer_details']['email'] ?? 'unknown';
            $reason = sprintf('Stripe Purchase: %s (%s)', $session['id'], $customer_email);
            CreditManager::add_credits($credits, $reason);
            Logger::info('Stripe Webhook: Credits added', ['credits' => $credits, 'session' => $session['id']]);
        } else {
            Logger::warning('Stripe Webhook: Could not determine credit amount for session', ['id' => $session['id']]);
        }
    }

    /**
     * Handle charge.refunded
     *
     * @param array $charge
     */
    private function handle_refund(array $charge): void {
        $amount   = $charge['amount_refunded'] ?? 0;
        $currency = strtolower($charge['currency'] ?? 'usd');
        
        $credits_to_remove = $this->map_amount_to_credits($amount, $currency);

        if ($credits_to_remove > 0) {
            $reason = sprintf('Stripe Refund: %s', $charge['id']);
            CreditManager::add_credits(-$credits_to_remove, $reason);
            Logger::info('Stripe Webhook: Credits removed (refund)', ['credits' => $credits_to_remove, 'charge' => $charge['id']]);
        }
    }

    /**
     * Map payment amount to credit count
     *
     * @param int    $amount Amount in cents
     * @param string $currency
     * @return int
     */
    private function map_amount_to_credits(int $amount, string $currency): int {
        // PLN mapping
        if ($currency === 'pln') {
            if ($amount >= 79900) return 5000;
            if ($amount >= 19900) return 1000;
            if ($amount >= 4900)  return 200;
        }

        // USD mapping
        if ($currency === 'usd') {
            if ($amount >= 14900) return 5000;
            if ($amount >= 3900)  return 1000;
            if ($amount >= 1200)  return 200;
        }

        return 0;
    }
}
