<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * .ics Exporter
 *
 * ============================================================================
 * TIMEZONE ARCHITECTURE NOTE
 * ============================================================================
 *
 * ICS files require proper timezone handling for external calendar applications.
 *
 * CURRENT BEHAVIOR:
 * - Uses X-WR-TIMEZONE header to indicate site timezone
 * - DTSTART/DTEND are formatted as local times (no Z suffix)
 * - External calendars interpret these as site timezone times
 *
 * IMPORTANT: Our stored timestamps ALREADY represent site timezone times
 * (stored as UTC but semantically meaning site TZ). When formatting for ICS:
 * - date('Ymd\THis', $timestamp) produces correct site-local time
 * - DO NOT add 'Z' suffix (that would indicate UTC)
 * - DO NOT perform timezone conversion
 *
 * For true UTC output (if ever needed for specific integrations):
 * $site_tz = new DateTimeZone( wc_timezone_string() );
 * $utc_tz = new DateTimeZone( 'UTC' );
 * $dt = new DateTime( date( 'Y-m-d H:i:s', $timestamp ), $site_tz );
 * $dt->setTimezone( $utc_tz );
 * $true_utc = $dt->format( 'Ymd\THis\Z' );
 *
 * See docs/TIMEZONE_ARCHITECTURE.md and docs/plans/TIMEZONE_MANAGEMENT_PLAN.md
 * ============================================================================
 */
class WC_Appointments_ICS_Exporter implements WC_Appointments_Exporter_Interface {

	/**
	 * Appointments list to export
	 *
	 * @var array
	 */
	protected array $appointments = [];

	/**
	 * File path
	 *
	 * @var string
	 */
	protected string $file_path = '';

	/**
	 * UID prefix.
	 *
	 * @var string
	 */
	protected string $uid_prefix = 'wc_appointments_';

	/**
	 * Whether to export add-ons.
	 *
	 * @var bool
	 */
	protected bool $export_addons = false;

	/**
	 * End of line.
	 *
	 * @var string
	 */
	protected string $eol = "\r\n";

	/**
	 * Get appointment .ics
	 *
	 * Generates an .ics file for a single appointment.
	 *
	 * @param  WC_Appointment $appointment Appointment data
	 *
	 * @return string Path to the generated .ics file.
	 */
	public function get_appointment_ics( $appointment ) {
		$product              = $appointment->get_product();
		$this->file_path      = $this->get_file_path( $appointment->get_id() . '-' . $product->get_title() );
		$this->appointments[] = $appointment;

		// Create the .ics
		$this->create();

		return $this->file_path;
	}

	/**
	 * Get .ics for appointments.
	 *
	 * Generates an .ics file for a list of appointments.
	 *
	 * @param  array  $appointments Array with WC_Appointment objects
	 * @param  string $filename .ics filename (without extension, or with .ics extension)
	 *
	 * @return string Path to the generated .ics file.
	 */
	public function get_ics( $appointments, $filename = '' ) {
		// Create a generic filename.
		if ( '' == $filename ) {
			$filename = 'appointments-' . date_i18n( 'Ymd-His', current_time( 'timestamp' ) );
		}

		// Remove .ics extension if present (we'll add it in get_file_path).
		$filename = preg_replace( '/\.ics$/i', '', $filename );
		// Remove any "-ics" suffix before extension.
		$filename = preg_replace( '/-ics$/i', '', $filename );

		$this->file_path    = $this->get_file_path( $filename );
		$this->appointments = $appointments;

		// Create the .ics
		$this->create();

		return $this->file_path;
	}

	/**
     * Get file path
     *
     * Generates the absolute file path for the .ics file based on the filename.
     *
     * @param  string $filename Filename
     * @return string Absolute path to the file.
     */
    protected function get_file_path( $filename ): string {
		$upload_data = wp_upload_dir();

		return $upload_data['path'] . '/' . sanitize_title( $filename ) . '.ics';
	}

	/**
	 * Create the .ics file
	 *
	 * Writes the generated ICS content to the file system.
	 *
	 * @return void
	 */
	protected function create() {
		// @codingStandardIgnoreStart
		$handle = @fopen( $this->file_path, 'w' );
		$ics    = $this->generate();
		@fwrite( $handle, $ics );
		@fclose( $handle );
		// @codingStandardIgnoreEnd
	}

	/**
	 * Format the date
	 *
	 * Formats a timestamp for ICS compatibility.
	 *
	 * @version 3.0.0
	 *
	 * @param int            $timestamp   Timestamp to format.
	 * @param WC_Appointment $appointment Appointment object.
	 *
	 * @return string Formatted date string.
	 */
	protected function format_date( $timestamp, $appointment = null ) {
		#$pattern = 'Ymd\THis\Z';
		$pattern = 'Ymd\THis';
		$old_ts  = $timestamp;

		if ( $appointment ) {
			$pattern = ( $appointment->is_all_day() ) ? 'Ymd' : $pattern;

			// If we're working on the end timestamp
            // If appointments are more than 1 day, ics format for the end date should be the day after the appointment ends
            if ( $appointment->get_end() === $timestamp && strtotime('midnight', $appointment->get_start()) !== strtotime( 'midnight', $appointment->get_end() ) ) {
				$timestamp += 86400;
			}
		}

		return apply_filters( 'woocommerce_appointments_ics_format_date', date( $pattern, $timestamp ), $timestamp, $old_ts, $appointment );
	}

	/**
	 * Sanitize strings for .ics
	 *
	 * Escapes special characters in strings for ICS format.
	 *
	 * @param  string $string Input string.
	 *
	 * @return string Sanitized string.
	 */
	protected function sanitize_string( $string ) {
		$string = preg_replace( '/([,;])/', '\\\$1', $string );
		$string = str_replace( "\n", '\n', $string );

		return sanitize_text_field( $string );
	}

	/**
	 * Generate the .ics content
	 *
	 * Builds the VCALENDAR content string including all appointments.
	 *
	 * @return string ICS content.
	 */
	protected function generate() {
		$sitename = get_option( 'blogname' );

		// Set the ics data.
		$ics  = 'BEGIN:VCALENDAR' . $this->eol;
		$ics .= 'VERSION:2.0' . $this->eol;
		$ics .= 'PRODID:-//BookingWP//WooCommerce Appointments ' . WC_APPOINTMENTS_VERSION . '//EN' . $this->eol;
		$ics .= 'CALSCALE:GREGORIAN' . $this->eol;
		$ics .= 'X-WR-CALNAME:' . $this->sanitize_string( $sitename ) . $this->eol;
		$ics .= 'X-ORIGINAL-URL:' . $this->sanitize_string( get_site_url( get_current_blog_id(), '/' ) ) . $this->eol;
		/* translators: %s: site name */
		$ics .= 'X-WR-CALDESC:' . $this->sanitize_string( sprintf( __( 'Appointments from %s', 'woocommerce-appointments' ), $sitename ) ) . $this->eol;
		$ics .= 'X-WR-TIMEZONE:' . wc_timezone_string() . $this->eol;

		// Set the ics appointment data.
		foreach ( $this->appointments as $appointment ) {
			$ics_appointment = $this->ics_appointment( $appointment );
			// Loop through ics data.
			foreach ( $ics_appointment as $ics_data ) {
				#error_log( var_export( $ics_data, true ) );
				$ics .= $ics_data . $this->eol;
			}
		}

		$ics .= 'END:VCALENDAR';

		return apply_filters( 'wc_appointments_ics_exporter', $ics, $this );
	}

	/**
	 * Generate the appointment ICS data.
	 *
	 * Builds the VEVENT content for a single appointment.
	 *
	 * @param WC_Appointment $appointment Appointment object.
	 * @return array Array of ICS lines.
	 */
	protected function ics_appointment( $appointment ) {
		$sitename  = get_option( 'blogname' );
		$siteadmin = get_option( 'admin_email' );

		// Set the ics data.
		$ics_a          = [];
		$appointment->get_id();
		$product        = $appointment->get_product();
		$product_title  = $product ? ' - ' . $product->get_title() : '';
		$url            = $appointment->get_order() ? $appointment->get_order()->get_view_order_url() : '';
		$summary        = '#' . $appointment->get_id() . $product_title;
		$description    = '';
		$date_prefix    = $appointment->is_all_day() ? ';VALUE=DATE:' : ':';
		$staff_names    = $appointment->get_staff_members( true );
		#$date_prefix    = $appointment->is_all_day() ? ';VALUE=DATE:' : ';TZID=/' . wc_timezone_string() . ':';

		if ( $staff_names ) {
			$description .= __( 'Staff', 'woocommerce-appointments' ) . ': ' . $staff_names . '\n\n';
		}

		$post_excerpt = $product ? get_post( $product->get_id() )->post_excerp : '';

		if ( '' !== $post_excerpt ) {
			$description .= __( 'Appointment description:', 'woocommerce-appointments' ) . '\n';
			$description .= wp_kses( $post_excerpt, [] );
		}

		// Include add-ons information if available and export_addons is enabled.
		if ( $this->export_addons ) {
			$addons = $appointment->get_addons(
				[
					'before'       => '',
					'after'        => '',
					'separator'    => ', ',
					'echo'         => false,
					'autop'        => false,
					'label_before' => '',
					'label_after'  => ': ',
				]
			);

			if ( ! empty( $addons ) ) {
				$description .= '\n\n' . __( 'Add-ons:', 'woocommerce-appointments' ) . '\n';
				$description .= wp_strip_all_tags( $addons );
			}
		}

		$ics_a['begin_vevent'] = 'BEGIN:VEVENT';
		$ics_a['dtstart']      = 'DTSTART' . $date_prefix . $this->format_date( $appointment->get_start(), $appointment );
		$ics_a['dtend']        = 'DTEND' . $date_prefix . $this->format_date( $appointment->get_end(), $appointment );
		$ics_a['uid']          = 'UID:' . $this->uid_prefix . $appointment->get_id();
		$ics_a['dtstamp']      = 'DTSTAMP:' . $this->format_date( current_time( 'timestamp' ) );
		$ics_a['location']     = 'LOCATION:';
		$ics_a['description']  = 'DESCRIPTION:' . $this->sanitize_string( $description );
		$ics_a['url']          = 'URL;VALUE=URI:' . $this->sanitize_string( $url );
		$ics_a['summary']      = 'SUMMARY:' . $this->sanitize_string( $summary );
		$ics_a['organizer']    = 'ORGANIZER;CN="' . $this->sanitize_string( $sitename ) . '":' . $this->sanitize_string( $siteadmin );
		$ics_a['end_vevent']   = 'END:VEVENT';

		return apply_filters( 'wc_appointments_ics_appointment', $ics_a, $appointment, $this );
	}

	/**
	 * Export appointments to a file (interface implementation).
	 *
	 * @param array $appointments Array of WC_Appointment objects.
	 * @param array $args Optional export arguments (e.g., 'filename', 'export_addons').
	 * @return string|false File path on success, false on failure.
	 */
	public function export( array $appointments, array $args = [] ): string|false {
		$filename = $args['filename'] ?? '';
		$this->export_addons = ! empty( $args['export_addons'] );
		return $this->get_ics( $appointments, $filename );
	}

	/**
	 * Get the export file format/mime type.
	 *
	 * @return string Format identifier.
	 */
	public function get_format(): string {
		return 'ics';
	}

	/**
	 * Get the file extension for this exporter.
	 *
	 * @return string File extension.
	 */
	public function get_file_extension(): string {
		return '.ics';
	}
}
