<?php
/**
 * Billing engine: invoice generation, renewals and the daily cron.
 *
 * @package Lucrativeit_Subscription_Recurring_Billing
 */

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

/**
 * Orchestrates invoice creation, payment dispatch and recurring renewals
 * for the built-in custom subscription engine.
 */
class LUCRSURE_Billing_Engine {

	/**
	 * Singleton.
	 *
	 * @var LUCRSURE_Billing_Engine|null
	 */
	private static $instance = null;

	/**
	 * Get the singleton.
	 *
	 * @return LUCRSURE_Billing_Engine
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Register cron handlers.
	 */
	public function init() {
		add_action( 'lucrsure_daily_billing_cron', array( $this, 'process_renewals' ) );

		if ( ! wp_next_scheduled( 'lucrsure_daily_billing_cron' ) ) {
			wp_schedule_event( time() + 60, 'daily', 'lucrsure_daily_billing_cron' );
		}
	}

	/**
	 * Begin billing for a newly created subscription.
	 *
	 * Stripe (automatic recurring): creates a Stripe Checkout subscription
	 * session so the card is saved and charged automatically each cycle; the
	 * customer is emailed a one-time setup link. Invoice-based gateways fall
	 * back to generating an invoice with a payment link.
	 *
	 * @param array $subscription Subscription row.
	 * @return array{payment_url:string,error:string}
	 */
	public function start_subscription( array $subscription ) {
		$result = array(
			'payment_url' => '',
			'error'       => '',
		);

		$manager = LUCRSURE_Gateway_Manager::instance();
		$gateway = $manager->get( $subscription['gateway'] );

		if ( $gateway && $gateway->supports_automatic_recurring() && method_exists( $gateway, 'create_recurring_checkout' ) ) {
			return $this->start_stripe_subscription( $subscription, $gateway );
		}

		// Invoice-based gateways: charge the first period now unless it is a
		// trial with no immediate payment.
		if ( 'trialing' !== $subscription['status'] ) {
			$invoice_id = $this->create_invoice_for_subscription( $subscription );
			if ( $invoice_id ) {
				$invoice = ( new LUCRSURE_Invoice_Repository() )->find( $invoice_id );
				if ( $invoice && ! empty( $invoice['payment_url'] ) ) {
					$result['payment_url'] = $invoice['payment_url'];
				}
			}
		}

		$this->notify_subscription_active( $subscription );

		return $result;
	}

	/**
	 * Email the customer when their subscription becomes active.
	 *
	 * @param array $subscription Subscription row.
	 */
	public function notify_subscription_active( array $subscription ) {
		if ( ! in_array( $subscription['status'], array( 'active', 'trialing' ), true ) ) {
			return;
		}

		$settings = get_option( 'lucrsure_settings', array() );
		$notify   = isset( $settings['notifications']['subscription_active'] )
			? $settings['notifications']['subscription_active']
			: true;

		if ( ! $notify ) {
			return;
		}

		$customers = new LUCRSURE_Customer_Repository();
		$customer  = $customers->find( (int) $subscription['customer_id'] );
		if ( ! $customer || empty( $customer['email'] ) ) {
			return;
		}

		$data = lucrsure_subscription_email_data( $subscription );
		if ( empty( $data ) ) {
			return;
		}

		LUCRSURE_Emails::send( 'subscription_active', $customer['email'], $data );
	}

	/**
	 * Create a Stripe subscription checkout and email the customer the setup link.
	 *
	 * @param array                 $subscription Subscription row.
	 * @param LUCRSURE_Stripe_Gateway    $gateway      Stripe gateway.
	 * @return array{payment_url:string,error:string}
	 */
	protected function start_stripe_subscription( array $subscription, $gateway ) {
		$result = array(
			'payment_url' => '',
			'error'       => '',
		);

		$plans     = new LUCRSURE_Plan_Repository();
		$customers = new LUCRSURE_Customer_Repository();
		$invoices  = new LUCRSURE_Invoice_Repository();

		$plan     = $plans->find( (int) $subscription['plan_id'] );
		$customer = $customers->find( (int) $subscription['customer_id'] );
		if ( ! $plan || ! $customer ) {
			$result['error'] = __( 'Could not load plan or customer for Stripe checkout.', 'lucrativeit-subscription-recurring-billing' );
			return $result;
		}

		$unit_price = lucrsure_plan_effective_price( $plan );
		$amount     = $unit_price * max( 1, (int) $subscription['quantity'] );
		$currency = $plan['currency'];

		// Record the first invoice (unpaid) so the period is tracked; the
		// webhook marks it paid once checkout completes.
		$invoice_id = $invoices->insert(
			array_merge(
				array(
					'invoice_number'  => $invoices->next_number(),
					'customer_id'     => (int) $customer['id'],
					'subscription_id' => (int) $subscription['id'],
					'amount'          => $amount,
					'currency'        => $currency,
					'status'          => 'unpaid',
					'due_date'        => gmdate( 'Y-m-d H:i:s', strtotime( '+7 days' ) ),
					'gateway'         => 'stripe',
					'line_items'      => wp_json_encode(
						array(
							array(
								'description' => $plan['name'],
								'quantity'    => (int) $subscription['quantity'],
								'unit_price'  => $unit_price,
							),
						)
					),
				),
				lucrsure_sanitize_billing_address( $customer )
			)
		);

		if ( ! $invoice_id ) {
			$result['error'] = __( 'Could not create invoice for Stripe checkout.', 'lucrativeit-subscription-recurring-billing' );
			return $result;
		}

		$invoice = $invoices->find( $invoice_id );
		if ( ! $invoice ) {
			$result['error'] = __( 'Could not load invoice for Stripe checkout.', 'lucrativeit-subscription-recurring-billing' );
			return $result;
		}

		$is_lifetime = 'lifetime' === sanitize_key( $plan['billing_interval'] );

		if ( $is_lifetime ) {
			$checkout = $gateway->create_payment( $invoice );
		} else {
			$checkout = $gateway->create_recurring_checkout(
				array(
					'amount'          => $amount,
					'currency'        => $currency,
					'interval'        => $plan['billing_interval'],
					'interval_count'  => (int) $plan['interval_count'],
					'trial_days'      => 'trialing' === $subscription['status'] ? (int) $plan['trial_days'] : 0,
					'customer_email'  => $customer['email'],
					'product_name'    => $plan['name'],
					'subscription_id' => (int) $subscription['id'],
					'invoice_number'  => $invoice['invoice_number'],
				)
			);
		}

		if ( empty( $checkout['payment_url'] ) ) {
			$result['error'] = ! empty( $checkout['message'] )
				? $checkout['message']
				: __( 'Stripe checkout could not be created. Add your secret key under Settings → Payment Gateways.', 'lucrativeit-subscription-recurring-billing' );
			return $result;
		}

		$invoices->update(
			$invoice_id,
			array(
				'payment_url'        => $checkout['payment_url'],
				'gateway_payment_id' => isset( $checkout['gateway_payment_id'] ) ? $checkout['gateway_payment_id'] : '',
			)
		);

		LUCRSURE_Emails::send(
			'invoice_payment_request',
			$customer['email'],
			array(
				'customer_name'  => $customer['name'],
				'invoice_number' => $invoice['invoice_number'],
				'invoice_id'     => (int) $invoice_id,
				'package_name'   => $plan['name'],
				'amount'         => $amount,
				'currency'       => $currency,
				'due_date'       => $invoice['due_date'],
				'gateway'        => 'stripe',
				'payment_url'    => $checkout['payment_url'],
			)
		);

		$result['payment_url'] = $checkout['payment_url'];

		return $result;
	}

	/**
	 * Record a successful recurring payment reported by a gateway webhook.
	 *
	 * @param array $subscription Local subscription row.
	 * @param float $amount       Amount paid.
	 * @param string $currency    Currency code.
	 * @param string $payment_id  Gateway payment id.
	 * @return int Invoice id.
	 */
	public function record_recurring_payment( array $subscription, $amount, $currency, $payment_id = '' ) {
		$invoices  = new LUCRSURE_Invoice_Repository();
		$plans     = new LUCRSURE_Plan_Repository();
		$customers = new LUCRSURE_Customer_Repository();
		$customer  = $customers->find( (int) $subscription['customer_id'] );

		$invoice_id = $invoices->insert(
			array_merge(
				array(
					'invoice_number'     => $invoices->next_number(),
					'customer_id'        => (int) $subscription['customer_id'],
					'subscription_id'    => (int) $subscription['id'],
					'amount'             => (float) $amount,
					'currency'           => $currency,
					'status'             => 'paid',
					'paid_at'            => current_time( 'mysql' ),
					'gateway'            => $subscription['gateway'],
					'gateway_payment_id' => (string) $payment_id,
				),
				lucrsure_sanitize_billing_address( is_array( $customer ) ? $customer : array() )
			)
		);

		// Advance the subscription period.
		$plan = $plans->find( (int) $subscription['plan_id'] );
		if ( $plan ) {
			$subs = new LUCRSURE_Subscription_Repository();
			$now  = current_time( 'mysql' );
			$subs->update(
				(int) $subscription['id'],
				array(
					'status'               => 'active',
					'current_period_start' => $now,
					'current_period_end'   => lucrsure_next_period_end( $plan['billing_interval'], (int) $plan['interval_count'], $now ),
				)
			);
		}

		$invoice = $invoices->find( $invoice_id );
		if ( $invoice ) {
			$this->notify_payment_received( $invoice );
		}

		return $invoice_id;
	}

	/**
	 * Create an invoice for a subscription and dispatch payment via its gateway.
	 *
	 * @param array $subscription Subscription row.
	 * @return int Invoice ID (0 on failure).
	 */
	public function create_invoice_for_subscription( array $subscription ) {
		$plans     = new LUCRSURE_Plan_Repository();
		$invoices  = new LUCRSURE_Invoice_Repository();
		$customers = new LUCRSURE_Customer_Repository();
		$settings  = get_option( 'lucrsure_settings', array() );

		$plan = $plans->find( (int) $subscription['plan_id'] );
		if ( ! $plan ) {
			return 0;
		}

		$customer   = $customers->find( (int) $subscription['customer_id'] );
		$unit_price = lucrsure_plan_effective_price( $plan );
		$amount     = $unit_price * max( 1, (int) $subscription['quantity'] );
		$currency   = $plan['currency'];
		$due_days   = isset( $settings['invoice_due_days'] ) ? (int) $settings['invoice_due_days'] : 7;

		$invoice_id = $invoices->insert(
			array_merge(
				array(
					'invoice_number'  => $invoices->next_number(),
					'customer_id'     => (int) $subscription['customer_id'],
					'subscription_id' => (int) $subscription['id'],
					'amount'          => $amount,
					'currency'        => $currency,
					'status'          => 'unpaid',
					'due_date'        => gmdate( 'Y-m-d H:i:s', strtotime( '+' . $due_days . ' days' ) ),
					'gateway'         => $subscription['gateway'],
					'line_items'      => wp_json_encode(
						array(
							array(
								'description' => $plan['name'],
								'quantity'    => (int) $subscription['quantity'],
								'unit_price'  => $unit_price,
							),
						)
					),
				),
				lucrsure_sanitize_billing_address( is_array( $customer ) ? $customer : array() )
			)
		);

		if ( ! $invoice_id ) {
			return 0;
		}

		$this->dispatch_payment( $invoice_id );

		return $invoice_id;
	}

	/**
	 * Generate a payment link (for invoice-based gateways) and email the customer.
	 *
	 * @param int $invoice_id Invoice ID.
	 */
	public function dispatch_payment( $invoice_id ) {
		$invoices  = new LUCRSURE_Invoice_Repository();
		$customers = new LUCRSURE_Customer_Repository();

		$invoice = $invoices->find( $invoice_id );
		if ( ! $invoice ) {
			return;
		}

		$customer = $customers->find( (int) $invoice['customer_id'] );
		if ( $customer ) {
			$invoice['customer_name']  = $customer['name'];
			$invoice['customer_email'] = $customer['email'];
		}

		$manager = LUCRSURE_Gateway_Manager::instance();
		$gateway = $manager->get( $invoice['gateway'] );
		if ( ! $gateway ) {
			$gateway = $manager->get( 'manual' );
		}

		$result = $gateway->create_payment( $invoice );

		if ( ! empty( $result['payment_url'] ) || ! empty( $result['gateway_payment_id'] ) ) {
			$invoices->update(
				$invoice_id,
				array(
					'payment_url'        => isset( $result['payment_url'] ) ? $result['payment_url'] : '',
					'gateway_payment_id' => isset( $result['gateway_payment_id'] ) ? $result['gateway_payment_id'] : '',
				)
			);
		}

		// Automatic-recurring gateways charge the card directly; invoice-based
		// gateways email the customer a payment link.
		$settings = get_option( 'lucrsure_settings', array() );
		$notify   = isset( $settings['notifications']['invoice_sent'] ) ? $settings['notifications']['invoice_sent'] : true;

		if ( $notify && $customer && ! $gateway->supports_automatic_recurring() ) {
			$gateway_id = ! empty( $invoice['gateway'] ) ? $invoice['gateway'] : 'manual';

			LUCRSURE_Emails::send(
				'invoice_payment_request',
				$customer['email'],
				array(
					'customer_name'  => $customer['name'],
					'invoice_number' => $invoice['invoice_number'],
					'invoice_id'     => (int) $invoice_id,
					'package_name'   => lucrsure_invoice_package_name( $invoice ),
					'amount'         => $invoice['amount'],
					'currency'       => $invoice['currency'],
					'due_date'       => $invoice['due_date'],
					'gateway'        => $gateway_id,
					'payment_url'    => lucrsure_email_payment_url( $gateway_id, isset( $result['payment_url'] ) ? $result['payment_url'] : '' ),
				)
			);
		}
	}

	/**
	 * Mark an invoice as paid and confirm the subscription period.
	 *
	 * @param int $invoice_id Invoice ID.
	 * @return array{marked:bool,notified:bool}
	 */
	public function mark_invoice_paid( $invoice_id ) {
		$result = array(
			'marked'   => false,
			'notified' => false,
		);

		$invoices = new LUCRSURE_Invoice_Repository();
		$invoice  = $invoices->find( $invoice_id );
		if ( ! $invoice ) {
			return $result;
		}

		if ( 'paid' === $invoice['status'] ) {
			return $result;
		}

		$invoices->update(
			$invoice_id,
			array(
				'status'  => 'paid',
				'paid_at' => current_time( 'mysql' ),
			)
		);

		$invoice['status']  = 'paid';
		$invoice['paid_at'] = current_time( 'mysql' );
		$result['marked']   = true;

		if ( $invoice['subscription_id'] ) {
			$subs = new LUCRSURE_Subscription_Repository();
			$sub  = $subs->find( (int) $invoice['subscription_id'] );
			if ( $sub && in_array( $sub['status'], array( 'trialing', 'pending' ), true ) ) {
				$subs->update( (int) $sub['id'], array( 'status' => 'active' ) );
				$this->notify_subscription_active( $subs->find( (int) $sub['id'] ) );
			}
		}

		$result['notified'] = $this->notify_payment_received( $invoice );

		return $result;
	}

	/**
	 * Email the customer a payment-received confirmation.
	 *
	 * @param array $invoice Invoice row.
	 * @return bool
	 */
	public function notify_payment_received( array $invoice ) {
		if ( ! lucrsure_payment_received_notifications_enabled() ) {
			return false;
		}

		$customers = new LUCRSURE_Customer_Repository();
		$customer  = $customers->find( (int) $invoice['customer_id'] );
		if ( ! $customer || ! is_email( $customer['email'] ) ) {
			return false;
		}

		return LUCRSURE_Emails::send(
			'payment_success',
			$customer['email'],
			lucrsure_invoice_email_data( $invoice, $customer )
		);
	}

	/**
	 * Cron: renew due subscriptions and flag overdue invoices.
	 */
	public function process_renewals() {
		$settings = get_option( 'lucrsure_settings', array() );

		// In WooCommerce mode, WooCommerce handles renewals.
		if ( isset( $settings['subscription_engine'] ) && 'woocommerce' === $settings['subscription_engine'] ) {
			return;
		}

		$subs  = new LUCRSURE_Subscription_Repository();
		$plans = new LUCRSURE_Plan_Repository();
		$now   = current_time( 'mysql' );

		$due     = $subs->due_for_renewal( $now );
		$manager = LUCRSURE_Gateway_Manager::instance();

		foreach ( $due as $sub ) {
			$plan = $plans->find( (int) $sub['plan_id'] );
			if ( ! $plan ) {
				continue;
			}

			// Skip gateways that charge automatically (e.g. Stripe) — their
			// renewals are handled by the gateway and confirmed via webhook.
			$gateway = $manager->get( $sub['gateway'] );
			if ( $gateway && $gateway->supports_automatic_recurring() ) {
				continue;
			}

			$this->create_invoice_for_subscription( $sub );

			$next_end = lucrsure_next_period_end( $plan['billing_interval'], (int) $plan['interval_count'], $now );
			$subs->update(
				(int) $sub['id'],
				array(
					'current_period_start' => $now,
					'current_period_end'   => $next_end,
				)
			);
		}

		// Flag overdue invoices.
		$this->flag_overdue_invoices();
	}

	/**
	 * Mark unpaid invoices past their due date as overdue.
	 */
	protected function flag_overdue_invoices() {
		global $wpdb;
		$now = current_time( 'mysql' );

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->prepare(
				"UPDATE %i SET status = 'overdue', updated_at = %s WHERE status = 'unpaid' AND due_date IS NOT NULL AND due_date < %s",
				lucrsure_table( 'invoices' ),
				$now,
				$now
			)
		);
	}
}
