<?php

namespace WPEventGenius\Common\Services;

use WPEventGenius\Common\Database;

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

/**
 * Creates and backfills evge_payment_items from cart line items.
 * Items can be created at submission (pending, payment_id = 0) and attached to a payment when one is created.
 */
class PaymentItemsService {

	/**
	 * @var Database
	 */
	protected $database;

	public function __construct( ?Database $database = null ) {
		$this->database = $database ? $database : new Database();
	}

	/**
	 * Create pending payment_items rows (payment_id = 0) at submission time.
	 * Call after insert_or_update_all() so registration_id exists. Replaces any existing pending items for this registration.
	 *
	 * @param int    $registration_id   Main registration ID (evge_registrations.id).
	 * @param object $registration_group RegistrationGroup with payment_handler and cart.
	 * @param object $payment_record    Optional payment record (for currency_code).
	 * @return int Number of payment_item rows inserted.
	 */
	public function create_pending_items_for_registration( $registration_id, $registration_group, $payment_record = null ) {
		$registration_id = (int) $registration_id;
		if ( $registration_id <= 0 ) {
			return 0;
		}
		$this->database->delete_pending_payment_items_for_registration( $registration_id );
		$rows = $this->build_payment_item_rows_from_cart( $registration_group, $payment_record );
		if ( empty( $rows ) ) {
			return 0;
		}
		$count = 0;


		// Resolve registration_id per row when not in meta (e.g. new regs before insert had entry_id 0).
		$slug_to_registration_id = $this->get_slug_to_registration_id_map( $registration_id, $registration_group );
		foreach ( $rows as $row ) {
			$row['payment_id'] = 0;
			if ( isset( $row['registration_id'] ) && (int) $row['registration_id'] > 0 ) {
				// Per-registration from line item meta (e.g. edit flow when entry_ids were set).
			} elseif ( isset( $row['_line_item_slug'] ) && isset( $slug_to_registration_id[ $row['_line_item_slug'] ] ) ) {
				$row['registration_id'] = $slug_to_registration_id[ $row['_line_item_slug'] ];
			} else {
				$row['registration_id'] = $registration_id;
			}
			unset( $row['_line_item_slug'] );
			if ( $this->database->insert_payment_item( $row ) !== false ) {
				$count++;
			}
		}
		return $count;
	}

	/**
	 * Build map from line item slug to registration_id (main + guests) for assigning payment_item rows.
	 *
	 * @param int    $main_registration_id Main registration entry ID.
	 * @param object $registration_group   RegistrationGroup (optional).
	 * @return array<string, int> Slug => registration_id.
	 */
	protected function get_slug_to_registration_id_map( $main_registration_id, $registration_group = null ) {
		$map = array();
		$main_registration_id = (int) $main_registration_id;
		if ( $main_registration_id <= 0 ) {
			return $map;
		}
		$map['registration_main'] = $main_registration_id;
		if ( $registration_group && method_exists( $registration_group, 'get_additional_guest_registrations' ) ) {
			$guests = $registration_group->get_additional_guest_registrations();
			foreach ( $guests as $guest_number => $guest_registration ) {
				$guest_id = method_exists( $guest_registration, 'get_entry_id' ) ? (int) $guest_registration->get_entry_id() : 0;
				if ( $guest_id > 0 ) {
					$map[ 'registration_guest_' . $guest_number ] = $guest_id;
				}
			}
		}
		// registration_main_1, registration_main_2, ... (any index maps to main).
		for ( $i = 1; $i <= 20; $i++ ) {
			$map[ 'registration_main_' . $i ] = $main_registration_id;
		}
		return $map;
	}

	/**
	 * Replace all payment_items for a registration with the current cart (edit flow).
	 * Deletes all existing items (pending and attached), then inserts new rows from the registration group cart.
	 * Preserves the existing payment_id when the registration already had items attached to a payment.
	 *
	 * @param int    $registration_id   Main registration ID (evge_registrations.id).
	 * @param object $registration_group RegistrationGroup with payment_handler and cart.
	 * @param object $payment_record    Optional payment record (for currency_code).
	 * @return int Number of payment_item rows inserted.
	 */
	public function replace_payment_items_for_registration( $registration_id, $registration_group, $payment_record = null ) {
		$registration_id = (int) $registration_id;
		if ( $registration_id <= 0 ) {
			return 0;
		}
		$existing = $this->database->get_payment_items_by_registration_id( $registration_id, false );
		// Keep new items attached to the same payment (completed or pending).
		$payment_id_to_use = 0;
		foreach ( $existing as $row ) {
			$pid = isset( $row['payment_id'] ) ? (int) $row['payment_id'] : 0;
			if ( $pid > 0 ) {
				$payment_id_to_use = $pid;
				break;
			}
		}
		// If no existing items were attached, use the registration's payment record (e.g. pending payment) when provided.
		if ( $payment_id_to_use === 0 && $payment_record && method_exists( $payment_record, 'get_transaction_id' ) ) {
			$tid = (int) $payment_record->get_transaction_id();
			if ( $tid > 0 ) {
				$payment_id_to_use = $tid;
			}
		}
		$rows = $this->build_payment_item_rows_from_cart( $registration_group, $payment_record );
		if ( empty( $rows ) ) {
			return 0;
		}
		// If existing breakdown matches the new one, skip replace to avoid unnecessary writes.
		$existing_breakdown = $this->breakdown_from_payment_item_rows( $existing );
		$new_breakdown     = $this->breakdown_from_payment_item_rows( $rows );
		if ( $existing_breakdown === $new_breakdown ) {
			return 0;
		}
		// Delete payment items for main and all guests in the group so we can replace with per-registration rows.
		$registration_ids_to_clear = array( $registration_id );
		if ( $registration_group && method_exists( $registration_group, 'get_additional_guest_registrations' ) ) {
			foreach ( $registration_group->get_additional_guest_registrations() as $guest_registration ) {
				$guest_id = method_exists( $guest_registration, 'get_entry_id' ) ? (int) $guest_registration->get_entry_id() : 0;
				if ( $guest_id > 0 ) {
					$registration_ids_to_clear[] = $guest_id;
				}
			}
		}
		$registration_ids_to_clear = array_unique( array_filter( $registration_ids_to_clear ) );
		foreach ( $registration_ids_to_clear as $rid ) {
			$this->database->delete_payment_items_for_registration( $rid );
		}
		$slug_to_registration_id = $this->get_slug_to_registration_id_map( $registration_id, $registration_group );
		$count = 0;
		foreach ( $rows as $row ) {
			$row['payment_id'] = $payment_id_to_use;
			if ( ! isset( $row['registration_id'] ) || (int) $row['registration_id'] <= 0 ) {
				$slug = isset( $row['_line_item_slug'] ) ? $row['_line_item_slug'] : '';
				$row['registration_id'] = isset( $slug_to_registration_id[ $slug ] ) ? $slug_to_registration_id[ $slug ] : $registration_id;
			}
			unset( $row['_line_item_slug'] );
			if ( $this->database->insert_payment_item( $row ) !== false ) {
				$count++;
			}
		}
		return $count;
	}

	/**
	 * Normalize payment item rows to a comparable breakdown (type_id => quantity).
	 * Used to compare existing vs new before replacing.
	 *
	 * @param array<int, array<string, mixed>> $rows Rows with event_registration_type_id and quantity.
	 * @return string JSON-encoded map of type_id => quantity (sorted by type_id) for comparison.
	 */
	protected function breakdown_from_payment_item_rows( array $rows ) {
		$by_type = array();
		foreach ( $rows as $row ) {
			$type_id = isset( $row['event_registration_type_id'] ) ? (int) $row['event_registration_type_id'] : 0;
			$qty     = isset( $row['quantity'] ) ? max( 0, (int) $row['quantity'] ) : 0;
			if ( $type_id > 0 ) {
				$by_type[ $type_id ] = isset( $by_type[ $type_id ] ) ? $by_type[ $type_id ] + $qty : $qty;
			}
		}
		ksort( $by_type );
		return wp_json_encode( $by_type );
	}

	/**
	 * Attach existing pending payment_items (payment_id = 0) for a registration to a payment.
	 *
	 * @param int $payment_id       Payment transaction_id (evge_payments.transaction_id).
	 * @param int $registration_id  Main registration ID (evge_registrations.id).
	 * @return int Number of rows updated.
	 */
	public function attach_payment_to_pending_items( $payment_id, $registration_id ) {
		return $this->database->update_payment_items_payment_id_for_registration( (int) $registration_id, (int) $payment_id );
	}

	/**
	 * Create or attach payment_items for a payment.
	 * If pending items exist for this registration, attaches them to the payment. Otherwise inserts new rows from the cart (legacy path).
	 *
	 * @param int    $transaction_id   Payment transaction_id (evge_payments.transaction_id).
	 * @param object $registration_group RegistrationGroup with payment_handler and cart.
	 * @param object $payment_record   Payment Record (for currency_code).
	 * @return int Number of payment_item rows (attached or inserted).
	 */
	public function create_items_for_payment( $transaction_id, $registration_group, $payment_record = null ) {
		$transaction_id  = (int) $transaction_id;
		$registration_id = $registration_group->get_main() ? (int) $registration_group->get_main()->get_entry_id() : 0;
		// Attach pending items for main and all guests (additional_guests have items per registration_id).
		$total_attached = 0;
		if ( $registration_id > 0 ) {
			$total_attached += $this->attach_payment_to_pending_items( $transaction_id, $registration_id );
		}
		if ( $registration_group && method_exists( $registration_group, 'get_additional_guest_registrations' ) ) {
			foreach ( $registration_group->get_additional_guest_registrations() as $guest_registration ) {
				$guest_id = method_exists( $guest_registration, 'get_entry_id' ) ? (int) $guest_registration->get_entry_id() : 0;
				if ( $guest_id > 0 ) {
					$total_attached += $this->attach_payment_to_pending_items( $transaction_id, $guest_id );
				}
			}
		}
		if ( $total_attached > 0 ) {
			return $total_attached;
		}
		$rows = $this->build_payment_item_rows_from_cart( $registration_group, $payment_record );
		if ( empty( $rows ) ) {
			return 0;
		}
		$slug_to_registration_id = $this->get_slug_to_registration_id_map( $registration_id, $registration_group );
		$count = 0;
		foreach ( $rows as $row ) {
			$row['payment_id'] = $transaction_id;
			if ( ! isset( $row['registration_id'] ) || (int) $row['registration_id'] <= 0 ) {
				$slug = isset( $row['_line_item_slug'] ) ? $row['_line_item_slug'] : '';
				$row['registration_id'] = isset( $slug_to_registration_id[ $slug ] ) ? $slug_to_registration_id[ $slug ] : ( $registration_id > 0 ? $registration_id : null );
			}
			unset( $row['_line_item_slug'] );
			if ( $this->database->insert_payment_item( $row ) !== false ) {
				$count++;
			}
		}
		return $count;
	}

	/**
	 * Build payment_item row data (without payment_id/registration_id) from registration group cart.
	 *
	 * @param object $registration_group RegistrationGroup with payment_handler and cart.
	 * @param object $payment_record    Optional payment record (for currency_code).
	 * @return array<int, array<string, mixed>> Array of row data suitable for insert_payment_item.
	 */
	protected function build_payment_item_rows_from_cart( $registration_group, $payment_record = null ) {
		$event_id = $registration_group->get_main()->get_registration_data( 'event_id' );
		$event_id = (int) $event_id;
		if ( $event_id <= 0 ) {
			return array();
		}
		$currency = $this->get_default_currency_snapshot();
		if ( $payment_record && method_exists( $payment_record, 'get_currency_code' ) ) {
			$code = $payment_record->get_currency_code();
			if ( ! empty( $code ) ) {
				$currency = substr( sanitize_text_field( $code ), 0, 3 );
			}
		}
		$payment_handler = $registration_group->payment_handler();
		if ( ! $payment_handler || ! $payment_handler->cart() ) {
			return array();
		}
		$line_items   = $payment_handler->cart()->get_line_items();
		if ( empty( $line_items ) ) {
			return array();
		}
		$default_type = $this->get_default_registration_type_for_event( $event_id );
		$rows         = array();
		foreach ( $line_items as $slug => $line_item ) {
			if ( ! is_object( $line_item ) || ! method_exists( $line_item, 'get_quantity' ) ) {
				continue;
			}
			$quantity = (int) $line_item->get_quantity();
			if ( $quantity < 1 ) {
				continue;
			}
			$label  = method_exists( $line_item, 'get_label' ) ? $line_item->get_label() : $slug;
			$amount = method_exists( $line_item, 'get_amount' ) ? (float) $line_item->get_amount() : 0;
			$meta   = method_exists( $line_item, 'get_meta' ) ? $line_item->get_meta() : null;
			$type_id = null;
			$template_id_snapshot = null;
			$type_name_snapshot  = null;
			$line_item_registration_id = null;
			if ( is_array( $meta ) ) {
				$type_id               = isset( $meta['event_registration_type_id'] ) ? (int) $meta['event_registration_type_id'] : null;
				$template_id_snapshot  = isset( $meta['template_id'] ) ? (int) $meta['template_id'] : null;
				$type_name_snapshot    = isset( $meta['name'] ) ? sanitize_text_field( $meta['name'] ) : null;
				$line_item_registration_id = isset( $meta['registration_id'] ) && (int) $meta['registration_id'] > 0 ? (int) $meta['registration_id'] : null;
			}
			// Keep slug so we can resolve registration_id when meta had 0 (e.g. before insert_or_update_all).
			$line_item_slug = $slug;
			$type_row = ( $type_id > 0 ) ? $this->database->get_event_registration_type( $type_id ) : null;
			if ( $type_row ) {
				$amount = $this->get_effective_type_price_dollars( $event_id, $type_row );
			}
			$unit_cents       = (int) round( $amount * 100 );
			$line_total_cents = (int) round( $amount * $quantity * 100 );
			if ( $type_id === null && $default_type ) {
				$type_id = (int) $default_type['id'];
				if ( $template_id_snapshot === null && isset( $default_type['template_id'] ) ) {
					$template_id_snapshot = $default_type['template_id'] !== null ? (int) $default_type['template_id'] : null;
				}
				if ( $type_name_snapshot === null && ! empty( $default_type['name'] ) ) {
					$type_name_snapshot = sanitize_text_field( $default_type['name'] );
				}
			}
			$row_data = array(
				'event_id'                               => $event_id,
				'label'                                  => sanitize_text_field( $label ),
				'line_item_kind'                         => 'registration_type',
				'currency_snapshot'                      => $currency,
				'unit_price_amount_snapshot'            => $unit_cents,
				'quantity'                               => $quantity,
				'line_total_amount_snapshot'            => $line_total_cents,
				'event_registration_type_id'            => $type_id,
				'registration_type_template_id_snapshot' => $template_id_snapshot,
				'registration_type_name_snapshot'       => $type_name_snapshot ? substr( $type_name_snapshot, 0, 255 ) : null,
			);
			if ( $line_item_registration_id !== null ) {
				$row_data['registration_id'] = $line_item_registration_id;
			}
			$row_data['_line_item_slug'] = $line_item_slug;
			$rows[] = $row_data;
		}
		return $rows;
	}

	/**
	 * Build payment_item row data from a type breakdown (type_id => quantity).
	 * Used when admin edits registration types: replace items with new breakdown.
	 *
	 * @param int   $event_id        Event post ID.
	 * @param array $breakdown       Map of event_registration_type_id (int) => quantity (int).
	 * @param array<string, mixed>|null $payment_record Optional payment row (for currency_code).
	 * @return array<int, array<string, mixed>> Array of row data suitable for insert_payment_item (no payment_id/registration_id).
	 */
	public function build_payment_item_rows_from_breakdown( $event_id, array $breakdown, $payment_record = null ) {
		$event_id = (int) $event_id;
		if ( $event_id <= 0 || empty( $breakdown ) ) {
			return array();
		}
		$currency = $this->get_default_currency_snapshot();
		if ( is_array( $payment_record ) && ! empty( $payment_record['currency_code'] ) ) {
			$currency = substr( sanitize_text_field( $payment_record['currency_code'] ), 0, 3 );
		}
		$rows = array();
		foreach ( $breakdown as $type_id => $quantity ) {
			$type_id   = (int) $type_id;
			$quantity  = max( 0, (int) $quantity );
			if ( $type_id <= 0 || $quantity <= 0 ) {
				continue;
			}
			$type_row = $this->database->get_event_registration_type( $type_id );
			if ( ! $type_row || ( isset( $type_row['event_id'] ) && (int) $type_row['event_id'] !== $event_id ) ) {
				continue;
			}
			$label   = isset( $type_row['name'] ) ? sanitize_text_field( (string) $type_row['name'] ) : '';
			$amount  = $this->get_effective_type_price_dollars( $event_id, $type_row );
			$unit_cents       = (int) round( $amount * 100 );
			$line_total_cents = (int) round( $amount * $quantity * 100 );
			$template_id = isset( $type_row['template_id'] ) ? (int) $type_row['template_id'] : null;
			$type_name  = isset( $type_row['name'] ) ? sanitize_text_field( (string) $type_row['name'] ) : null;
			$rows[] = array(
				'event_id'                               => $event_id,
				'label'                                  => $label,
				'line_item_kind'                         => 'registration_type',
				'currency_snapshot'                      => $currency,
				'unit_price_amount_snapshot'            => $unit_cents,
				'quantity'                               => $quantity,
				'line_total_amount_snapshot'            => $line_total_cents,
				'event_registration_type_id'            => $type_id,
				'registration_type_template_id_snapshot' => $template_id,
				'registration_type_name_snapshot'       => $type_name ? substr( $type_name, 0, 255 ) : null,
			);
		}
		return $rows;
	}

	/**
	 * Get the effective price for a registration type in dollars (for cart/display).
	 * When the type has use_event_price set, returns the event's cost; otherwise returns the type's price (price_amount in cents → dollars).
	 *
	 * @param int   $event_id Event post ID.
	 * @param array<string, mixed> $type Type row from get_event_registration_type or get_event_registration_types.
	 * @return float Price in dollars.
	 */
	public function get_effective_type_price_dollars( $event_id, $type ) {
		$use_event = ! empty( $type['use_event_price'] );
		if ( $use_event && $event_id > 0 ) {
			$cost = get_post_meta( (int) $event_id, 'evge_cost_amount', true );
			return is_numeric( $cost ) && (float) $cost >= 0 ? (float) $cost : 0.0;
		}
		$cents = isset( $type['price_amount'] ) ? (int) $type['price_amount'] : 0;
		return $cents >= 100 || $cents === 0 ? (float) ( $cents / 100 ) : (float) $cents;
	}

	/**
	 * Get default currency code for payment_item currency_snapshot (e.g. when no payment record provides one).
	 * Uses offline_currency setting when Pro is active.
	 *
	 * @return string Three-letter currency code.
	 */
	protected function get_default_currency_snapshot() {
		if ( class_exists( 'WPEventGenius\Pro\Utils\ProSettings' ) ) {
			$setting = \WPEventGenius\Pro\Utils\ProSettings::get( 'offline_currency', 'USD' );
			if ( ! empty( $setting ) && is_string( $setting ) ) {
				return substr( sanitize_text_field( $setting ), 0, 3 );
			}
		}
		return 'USD';
	}

	/**
	 * Get the default (generic) registration type for an event when no type is specified.
	 * Prefers a type named "General", otherwise the first active type by sort_order.
	 * Used so the existing form (single "event" line) can attribute payment_items to a type and the plugin works as before with event-level cost and restrictions.
	 *
	 * @param int $event_id Event post ID.
	 * @return array<string, mixed>|null Type row or null if event has no types.
	 */
	protected function get_default_registration_type_for_event( $event_id ) {
		$types = $this->database->get_event_registration_types( (int) $event_id );
		if ( empty( $types ) ) {
			return null;
		}
		foreach ( $types as $type ) {
			if ( ! empty( $type['status'] ) && $type['status'] === 'active' && isset( $type['name'] ) && trim( $type['name'] ) === 'General' ) {
				return $type;
			}
		}
		foreach ( $types as $type ) {
			if ( ! empty( $type['status'] ) && $type['status'] === 'active' ) {
				return $type;
			}
		}
		return null;
	}

	/**
	 * Ensure a payment has at least one payment_item (legacy backfill).
	 * If the payment already has any payment_items, returns without inserting.
	 * Otherwise creates one legacy row (label "General", line_item_kind registration_type, default type from event).
	 *
	 * @param int $transaction_id Payment transaction_id (evge_payments.transaction_id).
	 * @return bool True if items already existed or one legacy item was created; false on error.
	 */
	public function ensure_items_for_payment( $transaction_id ) {
		$transaction_id = (int) $transaction_id;
		if ( $transaction_id <= 0 ) {
			return false;
		}
		$existing = $this->database->get_payment_items( $transaction_id );
		if ( ! empty( $existing ) ) {
			return true;
		}
		$row = $this->database->query_payment_record( $transaction_id );
		if ( empty( $row ) ) {
			return false;
		}
		$registration_id = isset( $row['registration_id'] ) ? (int) $row['registration_id'] : 0;
		$event_id = 0;
		$currency = isset( $row['currency_code'] ) && ! empty( $row['currency_code'] ) ? substr( sanitize_text_field( $row['currency_code'] ), 0, 3 ) : $this->get_default_currency_snapshot();
		if ( $registration_id > 0 ) {
			$reg = $this->database->query_registration_record( $registration_id );
			if ( ! empty( $reg[0]['event_id'] ) ) {
				$event_id = (int) $reg[0]['event_id'];
			}
		}
		if ( $event_id <= 0 ) {
			return false;
		}
		$types = $this->database->get_event_registration_types( $event_id );
		$default_type = null;
		foreach ( $types as $t ) {
			if ( ! empty( $t['status'] ) && $t['status'] === 'active' ) {
				$default_type = $t;
				break;
			}
		}
		$label = __( 'General', 'event-genius' );
		$type_id = null;
		$template_id = null;
		$type_name = null;
		if ( $default_type ) {
			$label = isset( $default_type['name'] ) ? $default_type['name'] : $label;
			$type_id = (int) $default_type['id'];
			$template_id = isset( $default_type['template_id'] ) ? (int) $default_type['template_id'] : null;
			$type_name = isset( $default_type['name'] ) ? $default_type['name'] : null;
		}
		$gross = isset( $row['payment_gross'] ) ? (float) $row['payment_gross'] : 0;
		$total_cents = (int) round( $gross * 100 );
		$unit_cents = $total_cents;
		$legacy = array(
			'payment_id'                             => $transaction_id,
			'event_id'                               => $event_id,
			'label'                                  => $label,
			'line_item_kind'                         => 'registration_type',
			'currency_snapshot'                      => $currency,
			'unit_price_amount_snapshot'            => $unit_cents,
			'quantity'                               => 1,
			'line_total_amount_snapshot'            => $total_cents,
			'event_registration_type_id'            => $type_id,
			'registration_type_template_id_snapshot' => $template_id,
			'registration_type_name_snapshot'       => $type_name ? substr( $type_name, 0, 255 ) : null,
		);
		return $this->database->insert_payment_item( $legacy ) !== false;
	}
}
