<?php
/**
 * Recurrence Calculator Service
 *
 * Main service for calculating and managing virtual recurring event instances
 *
 * @package NemtlyBooking
 * @since   PBOOKING_SINCE
 */

namespace NemtlyBooking\Services;

use NemtlyBooking\Models\Virtual_Instance;
use NemtlyBooking\Core\Event_Creator;
use WP_Query;

/**
 * Recurrence Calculator class
 *
 * Orchestrates recurrence calculations and virtual instance management.
 *
 * WHY virtual instances instead of storing each occurrence in the database:
 * A weekly event running for 2 years = 104 rows. With 50 recurring events that's
 * 5,200+ rows. Virtual instances calculate dates on-the-fly from the master event's
 * recurrence rule, reducing DB storage by ~99%. Only exceptions (cancellations,
 * date overrides) are persisted. Trade-off: slightly more CPU per request, but
 * dramatically less data to manage, migrate, and keep consistent.
 *
 * @since PBOOKING_SINCE
 */
class Recurrence_Calculator {

    /**
     * Get all event instances (master + virtual) for a date range
     *
     * @since PBOOKING_SINCE
     *
     * @param string $start_date Start date (Y-m-d).
     * @param string $end_date   End date (Y-m-d).
     * @param array  $args       Query arguments.
     *
     * @return array Array of event instances (master events + virtual instances).
     */
    public function get_instances_for_range( $start_date, $end_date, $args = [] ) {
        $instances = [];

        // Get all master recurring events
        $recurring_events = $this->get_recurring_master_events( $args );

        foreach ( $recurring_events as $master_event ) {
            // Generate virtual instances for this master event
            $virtual_instances = $this->generate_virtual_instances(
                $master_event->ID,
                $start_date,
                $end_date
            );

            foreach ( $virtual_instances as $instance ) {
                $instances[] = $instance->to_array();
            }
        }

        // Get single (non-recurring) events in the range
        $single_events = $this->get_single_events_in_range( $start_date, $end_date, $args );
        $instances     = array_merge( $instances, $single_events );

        // Sort by event date
        usort( $instances, [ $this, 'sort_instances_by_date' ] );

        return $instances;
    }

    /**
     * Generate virtual instances for a master event
     *
     * @since PBOOKING_SINCE
     *
     * @param int    $master_event_id Master event post ID.
     * @param string $start_date      Start date (Y-m-d).
     * @param string $end_date        End date (Y-m-d).
     *
     * @return array Array of Virtual_Instance objects.
     */
    public function generate_virtual_instances( $master_event_id, $start_date, $end_date ) {
        // Read the master event's recurrence configuration (pattern like 'availability', 'daily', 'weekly', etc.)
        $recurrence_pattern = get_post_meta( $master_event_id, Event_Creator::META_RECURRENCE_PATTERN, true );
        $event_start_date   = get_post_meta( $master_event_id, Event_Creator::META_EVENT_DATE, true );

        if ( empty( $recurrence_pattern ) || 'none' === $recurrence_pattern ) {
            return [];
        }

        // Build a Recurrence_Rule from the simple pattern string + optional count/end-date limits.
        // Recurrence_Rule handles the date math (daily/weekly/monthly interval stepping),
        // or availability-based day matching for slot-based event types.
        $recurrence_count = get_post_meta( $master_event_id, Event_Creator::META_RECURRENCE_COUNT, true );
        $recurrence_end   = get_post_meta( $master_event_id, Event_Creator::META_RECURRENCE_END, true );

        $rule = Recurrence_Rule::create_rule( $recurrence_pattern, $recurrence_count, $recurrence_end );

        // For availability-based recurrence, load the availability schedule so
        // Recurrence_Rule can determine which days of the week to generate instances for.
        $availability = [];
        if ( 'availability' === $recurrence_pattern ) {
            $availability_raw = get_post_meta( $master_event_id, Event_Creator::META_AVAILABILITY, true );
            $availability     = is_array( $availability_raw ) ? $availability_raw : json_decode( $availability_raw, true );
            if ( ! is_array( $availability ) ) {
                $availability = [];
            }
        }

        // Generate Virtual_Instance objects for each calculated occurrence within [start_date, end_date].
        // Each instance inherits the master event's metadata but gets its own date.
        $recurrence_rule   = new Recurrence_Rule( $master_event_id, $rule, $event_start_date, $availability );
        $virtual_instances = $recurrence_rule->generate_instances( $start_date, $end_date );

        // Exceptions (cancellations, modifications) are stored as post meta on the master event,
        // keyed by date. Cancelled instances are filtered out here so they don't appear in listings.
        return array_filter(
            $virtual_instances,
            function ( $instance ) {
				return ! $instance->is_cancelled();
			}
        );
    }

    /**
     * Get a specific virtual instance
     *
     * @since PBOOKING_SINCE
     *
     * @param string $instance_id Virtual instance ID (master_id_date).
     *
     * @return Virtual_Instance|null Instance object or null if not found.
     */
    public function get_virtual_instance( $instance_id ) {
        // Instance IDs encode the master event ID + occurrence date in a single string,
        // e.g. "42_2025_03_15" means master event #42 on 2025-03-15.
        // This avoids needing a separate lookup table for virtual instances.
        $parts = explode( '_', $instance_id );
        if ( count( $parts ) !== 4 ) { // Format: masterid_YYYY_mm_dd
            return null;
        }

        $master_event_id = intval( $parts[0] );
        $date            = $parts[1] . '-' . $parts[2] . '-' . $parts[3];

        // Validate master event exists and has recurrence
        $post = get_post( $master_event_id );
        if ( ! $post || 'nemtly_event' !== $post->post_type ) {
            return null;
        }

        $recurrence_pattern = get_post_meta( $master_event_id, Event_Creator::META_RECURRENCE_PATTERN, true );
        if ( empty( $recurrence_pattern ) || 'none' === $recurrence_pattern ) {
            return null;
        }

        // Create and validate the virtual instance
        $instance = Virtual_Instance::create( $master_event_id, $date );

        // Check if this date would actually occur in the recurrence pattern
        if ( $this->is_valid_recurrence_date( $master_event_id, $date ) ) {
            return $instance;
        }

        return null;
    }

    /**
     * Create exception for a virtual instance
     *
     * @since PBOOKING_SINCE
     *
     * @param string $instance_id    Virtual instance ID.
     * @param array  $exception_data Exception data (status, title, etc.).
     *
     * @return bool True on success, false on failure.
     */
    public function create_exception( $instance_id, $exception_data ) {
        $instance = $this->get_virtual_instance( $instance_id );
        if ( ! $instance ) {
            return false;
        }

        // Exceptions are stored as a date-keyed array in the master event's post meta.
        // This lets individual occurrences be cancelled or modified without creating
        // separate database records for each virtual instance.
        $exceptions = get_post_meta( $instance->master_event_id, 'nemtly_recurrence_exceptions', true );
        if ( ! is_array( $exceptions ) ) {
            $exceptions = [];
        }

        // Add new exception
        $exceptions[ $instance->date ] = $exception_data;

        // Save updated exceptions
        return update_post_meta( $instance->master_event_id, 'nemtly_recurrence_exceptions', $exceptions );
    }

    /**
     * Cancel a virtual instance
     *
     * @since PBOOKING_SINCE
     *
     * @param string $instance_id Virtual instance ID.
     *
     * @return bool True on success, false on failure.
     */
    public function cancel_instance( $instance_id ) {
        return $this->create_exception( $instance_id, [ 'status' => 'cancelled' ] );
    }

    /**
     * Get recurring master events
     *
     * @since PBOOKING_SINCE
     *
     * @param array $args Query arguments.
     *
     * @return array Array of WP_Post objects.
     */
    private function get_recurring_master_events( $args = [] ) {
        $query_args = array_merge(
            [
				'post_type'      => 'nemtly_event',
				'posts_per_page' => -1,
				'meta_query'     => [
					'relation' => 'AND',
					[
						'key'     => Event_Creator::META_RECURRENCE_PATTERN,
						'value'   => 'none',
						'compare' => '!=',
					],
					[
						'key'     => Event_Creator::META_RECURRENCE_PATTERN,
						'value'   => '',
						'compare' => '!=',
					],
				],
			],
            $args
        );

        $query = new WP_Query( $query_args );
        return $query->posts;
    }

    /**
     * Get single (non-recurring) events in date range
     *
     * @since PBOOKING_SINCE
     *
     * @param string $start_date Start date (Y-m-d).
     * @param string $end_date   End date (Y-m-d).
     * @param array  $args       Query arguments.
     *
     * @return array Array of event data.
     */
    private function get_single_events_in_range( $start_date, $end_date, $args = [] ) {
        $query_args = array_merge(
            [
				'post_type'      => 'nemtly_event',
				'posts_per_page' => -1,
				'meta_query'     => [
					'relation' => 'AND',
					[
						'key'     => Event_Creator::META_EVENT_DATE,
						'value'   => $start_date,
						'compare' => '>=',
						'type'    => 'DATE',
					],
					[
						'key'     => Event_Creator::META_EVENT_DATE,
						'value'   => $end_date,
						'compare' => '<=',
						'type'    => 'DATE',
					],
					[
						'relation' => 'OR',
						[
							'key'     => Event_Creator::META_RECURRENCE_PATTERN,
							'value'   => 'none',
							'compare' => '=',
						],
						[
							'key'     => Event_Creator::META_RECURRENCE_PATTERN,
							'compare' => 'NOT EXISTS',
						],
					],
				],
			],
            $args
        );

        $query  = new WP_Query( $query_args );
        $events = [];

        foreach ( $query->posts as $post ) {
            $event_date = get_post_meta( $post->ID, Event_Creator::META_EVENT_DATE, true );
            $events[]   = [
                'id'                 => $post->ID,
                'is_virtual'         => false,
                'is_recurring'       => false,
                'instance_date'      => $event_date,
                'master_event_id'    => null,
                'title'              => [ 'rendered' => $post->post_title ],
                'content'            => [ 'rendered' => wp_kses_post( wpautop( $post->post_content ) ) ],
                'status'             => $post->post_status,
                'event_date'         => $event_date,
                'event_time'         => get_post_meta( $post->ID, Event_Creator::META_EVENT_TIME, true ),
                'event_duration'     => get_post_meta( $post->ID, Event_Creator::META_EVENT_DURATION, true ),
                'location'           => get_post_meta( $post->ID, Event_Creator::META_LOCATION, true ),
                'max_attendees'      => get_post_meta( $post->ID, Event_Creator::META_MAX_ATTENDEES, true ),
                'booking_cutoff'     => get_post_meta( $post->ID, Event_Creator::META_BOOKING_CUTOFF, true ),
                'allow_waitlist'     => get_post_meta( $post->ID, Event_Creator::META_ALLOW_WAITLIST, true ),
                'recurrence_pattern' => '',
                'created_at'         => $post->post_date,
                'updated_at'         => $post->post_modified,
            ];
        }

        return $events;
    }

    /**
     * Check if a date is valid for a recurrence pattern
     *
     * @since PBOOKING_SINCE
     *
     * @param int    $master_event_id Master event ID.
     * @param string $date           Date to validate (Y-m-d).
     *
     * @return bool True if valid recurrence date.
     */
    private function is_valid_recurrence_date( $master_event_id, $date ) {
        // Get a small range around the date and check if it appears
        $start_check = gmdate( 'Y-m-d', strtotime( $date . ' -1 day' ) );
        $end_check   = gmdate( 'Y-m-d', strtotime( $date . ' +1 day' ) );

        $instances = $this->generate_virtual_instances( $master_event_id, $start_check, $end_check );

        foreach ( $instances as $instance ) {
            if ( $instance->date === $date ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Sort instances by date
     *
     * @since PBOOKING_SINCE
     *
     * @param array $a First instance.
     * @param array $b Second instance.
     *
     * @return int Sort comparison result.
     */
    private function sort_instances_by_date( $a, $b ) {
        $date_a = is_object( $a ) ? $a->date : $a['event_date'];
        $date_b = is_object( $b ) ? $b->date : $b['event_date'];

        return strcmp( $date_a, $date_b );
    }

    /**
     * Get statistics for recurring events
     *
     * @since PBOOKING_SINCE
     *
     * @return array Statistics data.
     */
    public function get_recurrence_stats() {
        $stats = [
            'total_master_events'     => 0,
            'total_virtual_instances' => 0,
            'patterns_used'           => [],
            'exceptions_count'        => 0,
        ];

        $recurring_events             = $this->get_recurring_master_events();
        $stats['total_master_events'] = count( $recurring_events );

        foreach ( $recurring_events as $event ) {
            $pattern                            = get_post_meta( $event->ID, Event_Creator::META_RECURRENCE_PATTERN, true );
            $stats['patterns_used'][ $pattern ] = ( $stats['patterns_used'][ $pattern ] ?? 0 ) + 1;

            // Count exceptions
            $exceptions = get_post_meta( $event->ID, 'nemtly_recurrence_exceptions', true );
            if ( is_array( $exceptions ) ) {
                $stats['exceptions_count'] += count( $exceptions );
            }

            // Estimate virtual instances (next 30 days)
            $next_month                        = gmdate( 'Y-m-d', strtotime( '+30 days' ) );
            $instances                         = $this->generate_virtual_instances( $event->ID, gmdate( 'Y-m-d' ), $next_month );
            $stats['total_virtual_instances'] += count( $instances );
        }

        return $stats;
    }
}
