<?php
/**
 * Lead-to-customer promotion service.
 *
 * Orchestrates turning a captured lead into a tracked customer contact record.
 * Promotion is contact-only (D-07): it creates the customer and flips the lead,
 * but it does NOT create the vehicle. Vehicle creation is a separate explicit
 * step, driven by the lead-detail launchpad, which calls create_vehicle_from_lead()
 * on its own gated handler. This keeps the lead-to-job pipeline staged: promote
 * to customer, then create the vehicle, then create the work order.
 *
 * This is the first write from the leads table into the Phase 1 customers and
 * vehicles tables, so it is deliberately thin: it reuses the existing prepared
 * repositories rather than reimplementing inserts.
 *
 * Duplicate detection is delegated to the leads repository's
 * find_matching_customers() helper, which normalizes the phone with the exact
 * same rule as the customers repository, so a match never depends on formatting
 * (D-04). The service never silently blocks a promotion: it exposes matches so
 * the caller can warn staff and let them choose to link or create (D-05).
 *
 * Capability and nonce enforcement is the caller's responsibility. This service
 * performs NO current_user_can() or check_admin_referer() checks itself; the
 * Leads admin page gates every entry point (the promote action, the link
 * handler, and the create handler) before invoking these methods. Keeping the
 * gate at the controller boundary keeps the policy in one place.
 *
 * @package WrenchPress
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class WrenchPress_Promotion
 */
class WrenchPress_Promotion {

	/**
	 * Fully qualified vehicles table name.
	 *
	 * @return string
	 */
	protected static function vehicles_table() {
		global $wpdb;

		return $wpdb->prefix . 'wrenchpress_vehicles';
	}

	/**
	 * Find active customers that match a lead on normalized phone or exact email.
	 *
	 * Delegates entirely to the Phase 1-normalized dedup query so promotion never
	 * re-implements phone normalization with a divergent rule (D-04, dedup-drift
	 * guard). An empty array means no likely duplicate; a non-empty array is the
	 * caller's cue to render the warning interstitial.
	 *
	 * @param object $lead Lead snapshot (expects phone and email).
	 * @return array List of matching active customer row objects.
	 */
	public static function find_matches( $lead ) {
		$phone = isset( $lead->phone ) ? (string) $lead->phone : '';
		$email = isset( $lead->email ) ? (string) $lead->email : '';

		return WrenchPress_Leads_Repository::find_matching_customers( $phone, $email );
	}

	/**
	 * Create a vehicle row from a lead's vehicle snapshot, linked to a customer.
	 *
	 * Mirrors the explicit-format $wpdb->insert shape used across the plugin: the
	 * engine column is left at its default (the public form does not collect it)
	 * and every value is bound through an explicit $formats array, so no lead
	 * field is ever interpolated into SQL.
	 *
	 * @param int    $customer_id Owning customer id.
	 * @param object $lead        Lead snapshot (vehicle fields).
	 * @return int|false New vehicle id, or false on insert failure / bad customer.
	 */
	public static function create_vehicle_from_lead( $customer_id, $lead ) {
		global $wpdb;

		$customer_id = absint( $customer_id );

		if ( 0 === $customer_id ) {
			return false;
		}

		$now = current_time( 'mysql' );

		$row = array(
			'customer_id'  => $customer_id,
			'make'         => isset( $lead->make ) ? (string) $lead->make : '',
			'model'        => isset( $lead->model ) ? (string) $lead->model : '',
			'year'         => isset( $lead->year ) ? absint( $lead->year ) : 0,
			'vin'          => isset( $lead->vin ) ? (string) $lead->vin : '',
			'plate'        => isset( $lead->plate ) ? (string) $lead->plate : '',
			'mileage'      => isset( $lead->mileage ) ? WrenchPress_Reminders_Repository::mileage_to_canonical( absint( $lead->mileage ), WrenchPress_Settings_Page::mileage_unit() ) : 0,
			'fuel_type'    => isset( $lead->fuel_type ) ? (string) $lead->fuel_type : '',
			'transmission' => isset( $lead->transmission ) ? (string) $lead->transmission : '',
			'color'        => isset( $lead->color ) ? (string) $lead->color : '',
			'engine'       => '',
			'created_at'   => $now,
			'updated_at'   => $now,
		);

		$formats = array(
			'%d', // customer_id.
			'%s', // make.
			'%s', // model.
			'%d', // year.
			'%s', // vin.
			'%s', // plate.
			'%d', // mileage.
			'%s', // fuel_type.
			'%s', // transmission.
			'%s', // color.
			'%s', // engine.
			'%s', // created_at.
			'%s', // updated_at.
		);

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table write via the $wpdb array API, intentionally not object-cached.
		$inserted = $wpdb->insert( self::vehicles_table(), $row, $formats );

		if ( false === $inserted ) {
			return false;
		}

		return (int) $wpdb->insert_id;
	}

	/**
	 * Promote a lead into a brand-new customer contact record.
	 *
	 * Creates the customer through the Phase 1 prepared repository, then flips the
	 * lead to 'promoted' with the new customer id recorded for audit (D-06, D-07).
	 * Promotion is contact-only: it does NOT create the vehicle. The vehicle is
	 * created later by the launchpad's explicit create-vehicle step, which calls
	 * create_vehicle_from_lead(). The lead row is retained, not deleted. Returns
	 * the new customer id, or false if the customer insert failed.
	 *
	 * @param object $lead Lead snapshot.
	 * @return int|false New customer id, or false on failure.
	 */
	public static function promote_new( $lead ) {
		$customer_id = WrenchPress_Customers_Repository::create(
			array(
				'first_name' => isset( $lead->first_name ) ? (string) $lead->first_name : '',
				'last_name'  => isset( $lead->last_name ) ? (string) $lead->last_name : '',
				'phone'      => isset( $lead->phone ) ? (string) $lead->phone : '',
				'email'      => isset( $lead->email ) ? (string) $lead->email : '',
				'address'    => isset( $lead->address ) ? (string) $lead->address : '',
				'notes'      => 'Promoted from visit request',
			)
		);

		if ( false === $customer_id || 0 === (int) $customer_id ) {
			return false;
		}

		$customer_id = (int) $customer_id;

		$lead_id = isset( $lead->id ) ? absint( $lead->id ) : 0;

		if ( $lead_id > 0 ) {
			WrenchPress_Leads_Repository::set_promoted( $lead_id, $customer_id );
		}

		return $customer_id;
	}

	/**
	 * Promote a lead by linking it to an existing customer contact record.
	 *
	 * Verifies the target customer exists and flips the lead to 'promoted' against
	 * that customer id. Like promote_new(), this is contact-only and does NOT
	 * create the vehicle; the launchpad's create-vehicle step attaches the vehicle
	 * later. Never creates a duplicate customer record (D-05/D-06/D-07).
	 *
	 * @param object $lead                 Lead snapshot.
	 * @param int    $existing_customer_id Customer to link the lead to.
	 * @return bool True on success, false on a missing customer.
	 */
	public static function promote_link( $lead, $existing_customer_id ) {
		$existing_customer_id = absint( $existing_customer_id );

		if ( 0 === $existing_customer_id ) {
			return false;
		}

		$customer = WrenchPress_Customers_Repository::get( $existing_customer_id );

		if ( ! $customer ) {
			return false;
		}

		$lead_id = isset( $lead->id ) ? absint( $lead->id ) : 0;

		if ( $lead_id > 0 ) {
			WrenchPress_Leads_Repository::set_promoted( $lead_id, $existing_customer_id );
		}

		return true;
	}
}
