<?php

declare( strict_types=1 );

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching

/**
 * Returns the base URL for customer portal links (password reset, cancel emails).
 * Users create their portal page manually, so we fall back to the home URL.
 */
function playard_get_portal_url(): string {
	return home_url( '/' );
}

/**
 * Returns the facility's DateTimeZone object.
 *
 * Reads the timezone string from the facility row (e.g. "America/New_York",
 * "Asia/Dhaka"). Falls back to America/New_York if unset or invalid.
 * Result is cached per-request so the DB is only hit once.
 */
function playard_facility_tz(): DateTimeZone {
	static $tz = null;
	if ( null !== $tz ) {
		return $tz;
	}
	$facility = playard_get_facility();
	$tzstr    = $facility ? trim( (string) ( $facility->timezone ?? '' ) ) : '';
	if ( $tzstr === '' ) {
		$tzstr = 'America/New_York';
	}
	try {
		$tz = new DateTimeZone( $tzstr );
	} catch ( \Exception $e ) {
		$tz = new DateTimeZone( 'America/New_York' );
	}
	return $tz;
}

/**
 * Returns the current date or time in the facility's local timezone.
 *
 * Use this everywhere the code needs "now" relative to the facility —
 * booking date guards, cron completion, no-show checks, report periods.
 *
 * @param string $format  PHP date format, e.g. 'Y-m-d', 'H:i:s', 'H:i'.
 * @return string
 */
function playard_now( string $format ): string {
	return ( new DateTime( 'now', playard_facility_tz() ) )->format( $format );
}

/**
 * Returns the reply-to email address for outgoing facility emails.
 * Uses the facility's contact email when set; falls back to the WP admin email.
 */
function playard_get_reply_to(): string {
	$facility = playard_get_facility();

	if ( $facility && ! empty( $facility->email ) && is_email( $facility->email ) ) {
		return sanitize_email( $facility->email );
	}

	return (string) get_option( 'admin_email', '' );
}

/**
 * Returns true when Playard Pro is active.
 * Pro plugin hooks into this filter and returns true.
 */
function playard_is_pro(): bool {
	return (bool) apply_filters( 'playard_is_pro', false );
}

/**
 * Returns the facility ID stored on activation / onboarding.
 */
function playard_get_facility_id(): int {
	return (int) get_option( 'playard_facility_id', 0 );
}

/**
 * Returns true when the given date (YYYY-MM-DD) is a facility holiday.
 * Reads directly from playard_holidays option — no class instantiation needed.
 */
function playard_is_holiday( string $date ): bool {
	$raw      = (string) get_option( 'playard_holidays', '[]' );
	$holidays = json_decode( $raw, true );

	if ( ! is_array( $holidays ) ) {
		return false;
	}

	foreach ( $holidays as $h ) {
		if ( isset( $h['date'] ) && $h['date'] === $date ) {
			return true;
		}
	}

	return false;
}

/**
 * Returns the active facility row from the DB, or null if none exists.
 */
function playard_get_facility(): ?object {
	$id = playard_get_facility_id();
	if ( $id === 0 ) {
		return null;
	}

	global $wpdb;

	$row = $wpdb->get_row(
		$wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}playard_facilities WHERE id = %d LIMIT 1",
			$id
		)
	);

	return $row ?: null;
}

/**
 * Returns the public booking page URL.
 * Uses the dedicated booking page when set, otherwise falls back to a query-arg URL.
 */
function playard_get_booking_url(): string {
	$page_id = (int) get_option( 'playard_booking_page_id', 0 );

	if ( $page_id > 0 ) {
		$permalink = get_permalink( $page_id );
		return $permalink ? (string) $permalink : add_query_arg( 'playard', 'booking', home_url( '/' ) );
	}

	return add_query_arg( 'playard', 'booking', home_url( '/' ) );
}

/**
 * Generates the next booking reference number.
 * Format: PY-YYYY-NNNNN  e.g. PY-2026-00001
 */
function playard_generate_booking_ref(): string {
	global $wpdb;

	$prefix = (string) ( ( (array) get_option( 'playard_settings', [] ) )['booking_ref_prefix'] ?? 'PY' );
	$year   = gmdate( 'Y' );

	// Atomic counter: ensure the row exists, then increment in a single SQL statement.
	// File-level phpcs:disable (line 7) covers DirectQuery/NoCaching for the whole file.
	add_option( 'playard_booking_counter', 0, '', 'no' );
	$wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- table and column names are hardcoded
		"UPDATE {$wpdb->options} SET option_value = LAST_INSERT_ID(CAST(option_value AS UNSIGNED) + 1) WHERE option_name = 'playard_booking_counter'"
	);
	$counter = (int) $wpdb->get_var( 'SELECT LAST_INSERT_ID()' );

	return sprintf( '%s-%s-%05d', $prefix, $year, $counter );
}

/**
 * Returns a standard success REST response array.
 *
 * @param mixed  $data
 * @param string $message
 */
function playard_rest_success( mixed $data = [], string $message = '' ): array {
	$response = [
		'success' => true,
		'data'    => $data,
	];
	if ( $message !== '' ) {
		$response['message'] = $message;
	}
	return $response;
}

/**
 * Returns a standard error REST response array.
 *
 * @param string $code    Machine-readable error code.
 * @param string $message Human-readable message.
 */
function playard_rest_error( string $code, string $message ): array {
	return [
		'success' => false,
		'code'    => $code,
		'message' => $message,
	];
}

/**
 * Returns a field row by ID, or null if not found.
 */
function playard_get_field( int $id ): ?object {
	global $wpdb;

	$row = $wpdb->get_row(
		$wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}playard_fields WHERE id = %d LIMIT 1",
			$id
		)
	);

	return $row ?: null;
}

/**
 * Returns the active facility currency code in lowercase (e.g. 'usd', 'bdt').
 * Used by Stripe — must match a Stripe-supported currency.
 */
function playard_get_currency(): string {
	$facility = playard_get_facility();
	$code     = $facility ? (string) $facility->currency : 'USD';
	return strtolower( $code );
}

/**
 * Formats a Y-m-d date string for display (e.g. "June 15, 2026").
 */
function playard_format_date( string $date ): string {
	$ts = strtotime( $date );
	return $ts ? wp_date( 'F j, Y', $ts ) : $date;
}

/**
 * Formats an HH:MM time string for display (e.g. "9:00 AM").
 * Respects the playard time_format setting (12 or 24 hour).
 */
function playard_format_time( string $time ): string {
	$settings = (array) get_option( 'playard_settings', [] );
	$format   = ( ( $settings['time_format'] ?? '12h' ) === '24h' ) ? 'H:i' : 'g:i A';
	$ts       = strtotime( '1970-01-01 ' . $time );
	return $ts ? wp_date( $format, $ts ) : $time;
}

/**
 * Returns the config object passed to the frontend booking widget via data-config.
 * All values are sanitized before output — the caller escapes with esc_attr(wp_json_encode()).
 *
 * @return array<string,mixed>
 */
function playard_get_frontend_config(): array {
	$facility = playard_get_facility();
	$settings = (array) get_option( 'playard_settings', [] );

	if ( ! $facility ) {
		return [ 'error' => 'Facility not configured' ];
	}

	$currency = (string) $facility->currency;

	return apply_filters( 'playard_frontend_config', [
		'apiBase'              => rest_url( 'playard/v1/' ),
		'nonce'                => wp_create_nonce( 'wp_rest' ),
		'currencySymbol'       => playard_get_currency_symbol( $currency ),
		'currencyCode'         => strtolower( $currency ),
		'currencyPosition'     => (string) apply_filters( 'playard_currency_position', 'before', $currency ),
		'timezone'             => (string) ( $facility->timezone ?? 'UTC' ),
		'dateFormat'           => $settings['date_format'] ?? 'DD/MM/YYYY',
		'timeFormat'           => ( ( $settings['time_format'] ?? '12h' ) === '24h' ) ? '24' : '12',
		'stripePublishableKey' => class_exists( 'Playard_Payment' ) ? ( new Playard_Payment() )->get_stripe_publishable_key() : '',
		'stripeConfigured'     => class_exists( 'Playard_Payment' ) && ( new Playard_Payment() )->is_stripe_configured(),
		'facilityName'         => sanitize_text_field( (string) $facility->name ),
		'facilityPhone'        => sanitize_text_field( (string) ( $facility->phone ?? '' ) ),
		'facilityAddress'      => sanitize_text_field( (string) ( $facility->address ?? '' ) ),
		'facilityLogo'         => esc_url_raw( (string) ( $facility->logo_url ?? '' ) ),
		'facilityCover'        => esc_url_raw( (string) ( $facility->cover_photo_url ?? '' ) ),
		'cancellationPolicy'   => $settings['cancellation'] ?? [],
		'bookingUrl'           => playard_get_booking_url(),
		'fields'               => '',
	] );
}

/**
 * Extracts and validates a customer JWT from the Authorization header.
 * Returns the customer row object, or WP_Error on auth failure.
 * Used as a shared helper by all authenticated customer endpoints.
 *
 * @param  WP_REST_Request $request
 * @return object|WP_Error  Customer row (stdClass) on success, WP_Error on failure.
 */
function playard_get_authenticated_customer( WP_REST_Request $request ) {
	$header = (string) $request->get_header( 'authorization' );

	if ( empty( $header ) || ! str_starts_with( $header, 'Bearer ' ) ) {
		return new WP_Error( 'auth_required', __( 'Authentication required.', 'playard' ), [ 'status' => 401 ] );
	}

	$token = trim( substr( $header, 7 ) );

	if ( ! class_exists( 'Playard_Customer' ) ) {
		return new WP_Error( 'auth_invalid', __( 'Authentication unavailable.', 'playard' ), [ 'status' => 401 ] );
	}

	$customer_id = ( new Playard_Customer() )->validate_jwt( $token );

	if ( ! $customer_id ) {
		return new WP_Error( 'auth_invalid', __( 'Invalid or expired token.', 'playard' ), [ 'status' => 401 ] );
	}

	global $wpdb;
	$customer = $wpdb->get_row( $wpdb->prepare(
		"SELECT * FROM {$wpdb->prefix}playard_customers WHERE id = %d LIMIT 1",
		$customer_id
	) );

	if ( ! $customer ) {
		return new WP_Error( 'auth_invalid', __( 'Account not found.', 'playard' ), [ 'status' => 401 ] );
	}

	return $customer;
}

/**
 * Returns an existing customer by phone, or creates a new record.
 * Delegates to Playard_Customer when the class is loaded.
 * Pro hooks may still override via the filter.
 *
 * @param  array<string,mixed> $data  Keys: name, phone, email (optional)
 * @return object  Customer row with at minimum ->id
 */
function playard_get_or_create_customer( array $data ): object {
	if ( class_exists( 'Playard_Customer' ) ) {
		return ( new Playard_Customer() )->get_or_create( $data );
	}

	return (object) apply_filters(
		'playard_get_or_create_customer',
		[ 'id' => 0 ],
		$data
	);
}

/**
 * Geocode a plain-text address to lat/lng via OpenStreetMap Nominatim.
 * Result is cached as a WordPress transient for 24 hours so we only
 * hit the external API once per unique address.
 *
 * @param  string $address  Full address string.
 * @return array{ lat: float, lng: float }|null  Null on failure.
 */
function playard_geocode_address( string $address ): ?array {
	if ( empty( trim( $address ) ) ) return null;

	$cache_key = 'playard_geo_' . md5( $address );
	$cached    = get_transient( $cache_key );

	if ( $cached !== false ) {
		return is_array( $cached ) ? $cached : null;
	}

	$url = 'https://nominatim.openstreetmap.org/search?' . http_build_query( [
		'q'      => $address,
		'format' => 'json',
		'limit'  => 1,
	] );

	$response = wp_remote_get( $url, [
		'timeout' => 6,
		'headers' => [ 'User-Agent' => 'Playard WordPress Plugin/1.0 (sports-facility-booking)' ],
	] );

	if ( is_wp_error( $response ) ) {
		set_transient( $cache_key, 0, HOUR_IN_SECONDS ); // short retry window on error
		return null;
	}

	$data = json_decode( wp_remote_retrieve_body( $response ), true );

	if ( empty( $data[0]['lat'] ) || empty( $data[0]['lon'] ) ) {
		set_transient( $cache_key, 0, HOUR_IN_SECONDS );
		return null;
	}

	$result = [
		'lat' => (float) $data[0]['lat'],
		'lng' => (float) $data[0]['lon'],
	];

	set_transient( $cache_key, $result, DAY_IN_SECONDS );
	return $result;
}
