<?php
/**
 * Calendar Widget for BoostBot
 *
 * Appointment scheduling widget.
 *
 * @package BoostBot
 * @since 1.0.0
 */

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

/**
 * Class BoostBot_Calendar_Widget
 *
 * Calendar widget for scheduling appointments.
 */
class BoostBot_Calendar_Widget extends BoostBot_Widget_Base {

    /**
     * Widget ID.
     *
     * @var string
     */
    protected $id = 'calendar';

    /**
     * Pro only widget.
     *
     * @var bool
     */
    protected $is_free = false;

    /**
     * Get widget name.
     *
     * @since 1.0.0
     * @return string Widget name.
     */
    public function get_name(): string {
        return __( 'Calendar', 'boostbot' );
    }

    /**
     * Get widget description.
     *
     * @since 1.0.0
     * @return string Widget description.
     */
    public function get_description(): string {
        return __( 'Schedule appointments and meetings', 'boostbot' );
    }

    /**
     * Get widget icon.
     *
     * @since 1.0.0
     * @return string Icon name.
     */
    public function get_icon(): string {
        return 'calendar';
    }

    /**
     * Get widget schema.
     *
     * @since 1.0.0
     * @return array Widget schema.
     */
    public function get_schema(): array {
        return array(
            'fields' => array(
                array(
                    'name'     => 'date',
                    'type'     => 'date',
                    'label'    => __( 'Date', 'boostbot' ),
                    'required' => true,
                ),
                array(
                    'name'     => 'time',
                    'type'     => 'time',
                    'label'    => __( 'Time', 'boostbot' ),
                    'required' => true,
                ),
                array(
                    'name'          => 'name',
                    'type'          => 'text',
                    'label'         => __( 'Full Name', 'boostbot' ),
                    'required'      => true,
                    'focus_message' => __( 'What name should we book the meeting under?', 'boostbot' ),
                ),
                array(
                    'name'          => 'email',
                    'type'          => 'email',
                    'label'         => __( 'Email', 'boostbot' ),
                    'required'      => true,
                    'focus_message' => __( 'We will send confirmation here', 'boostbot' ),
                ),
                array(
                    'name'          => 'phone',
                    'type'          => 'tel',
                    'label'         => __( 'Phone', 'boostbot' ),
                    'required'      => false,
                    'focus_message' => __( 'For contact in case of changes', 'boostbot' ),
                ),
            ),
            'submit_text'     => __( 'Book Appointment', 'boostbot' ),
            'success_message' => __( 'Appointment booked! Confirmation will be sent by email', 'boostbot' ),
            'settings'        => array(
                'min_date'          => 'today',
                'max_date'          => '+30 days',
                'available_days'    => array( 0, 1, 2, 3, 4 ), // Sun-Thu
                'available_hours'   => array(
                    'start' => '09:00',
                    'end'   => '18:00',
                ),
                'slot_duration'     => 30, // minutes
                'buffer_time'       => 15, // minutes between slots
            ),
        );
    }

    /**
     * Get widget configuration.
     *
     * @since 1.0.0
     * @param array $data Optional custom data.
     * @return array Widget configuration.
     */
    public function get_config( array $data = array() ): array {
        $config = parent::get_config( $data );

        // Add available time slots
        $config['available_slots'] = $this->get_available_slots( $data );

        // Add blocked dates
        $config['blocked_dates'] = $this->get_blocked_dates();

        return $config;
    }

    /**
     * Get available time slots.
     *
     * @since 1.0.0
     * @param array $data Request data.
     * @return array Available slots by date.
     */
    private function get_available_slots( array $data ): array {
        $schema = $this->get_schema();
        $settings = $schema['settings'];

        // Get date range
        $start_date = new DateTime( 'today' );
        $end_date = new DateTime( '+30 days' );

        $slots = array();
        $current = clone $start_date;

        while ( $current <= $end_date ) {
            $day_of_week = (int) $current->format( 'w' );

            // Check if day is available
            if ( in_array( $day_of_week, $settings['available_days'], true ) ) {
                $date_string = $current->format( 'Y-m-d' );
                $slots[ $date_string ] = $this->get_day_slots( $date_string, $settings );
            }

            $current->modify( '+1 day' );
        }

        return $slots;
    }

    /**
     * Get available slots for a specific day.
     *
     * @since 1.0.0
     * @param string $date     Date string (Y-m-d).
     * @param array  $settings Calendar settings.
     * @return array Time slots.
     */
    private function get_day_slots( string $date, array $settings ): array {
        $slots = array();

        $start = DateTime::createFromFormat( 'Y-m-d H:i', $date . ' ' . $settings['available_hours']['start'] );
        $end = DateTime::createFromFormat( 'Y-m-d H:i', $date . ' ' . $settings['available_hours']['end'] );

        if ( ! $start || ! $end ) {
            return $slots;
        }

        $duration = $settings['slot_duration'];
        $buffer = $settings['buffer_time'];
        $interval = $duration + $buffer;

        $current = clone $start;

        while ( $current < $end ) {
            $slot_end = clone $current;
            $slot_end->modify( "+{$duration} minutes" );

            if ( $slot_end <= $end ) {
                $slots[] = array(
                    'time'      => $current->format( 'H:i' ),
                    'available' => $this->is_slot_available( $date, $current->format( 'H:i' ) ),
                );
            }

            $current->modify( "+{$interval} minutes" );
        }

        return $slots;
    }

    /**
     * Check whether a requested time conforms to the configured availability.
     *
     * Enforces the available-hours window and the slot grid (slot_duration +
     * buffer_time) derived from the widget settings, mirroring how
     * get_day_slots() generates the offered slots.
     *
     * @since 1.0.0
     * @param string $time     Requested time (H:i).
     * @param array  $settings Calendar settings.
     * @return bool True if the time is a valid, on-grid slot inside the window.
     */
    private function is_time_within_availability( string $time, array $settings ): bool {
        $start = DateTime::createFromFormat( 'H:i', $settings['available_hours']['start'] );
        $end   = DateTime::createFromFormat( 'H:i', $settings['available_hours']['end'] );
        $slot  = DateTime::createFromFormat( 'H:i', $time );

        if ( ! $start || ! $end || ! $slot ) {
            return false;
        }

        $duration = (int) $settings['slot_duration'];
        $buffer   = (int) $settings['buffer_time'];
        $interval = $duration + $buffer;

        // The slot must start no earlier than opening and end no later than closing.
        $slot_end = clone $slot;
        $slot_end->modify( "+{$duration} minutes" );
        if ( $slot < $start || $slot_end > $end ) {
            return false;
        }

        // The slot must land on the grid stepped from the opening time.
        if ( $interval > 0 ) {
            $start_minutes = ( (int) $start->format( 'H' ) * 60 ) + (int) $start->format( 'i' );
            $slot_minutes  = ( (int) $slot->format( 'H' ) * 60 ) + (int) $slot->format( 'i' );
            if ( ( $slot_minutes - $start_minutes ) % $interval !== 0 ) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check if a specific slot is available.
     *
     * @since 1.0.0
     * @param string $date Date string.
     * @param string $time Time string.
     * @return bool True if available.
     */
    private function is_slot_available( string $date, string $time ): bool {
        // Check existing appointments
        $existing = get_option( 'boostbot_appointments', array() );

        $slot_key = $date . '_' . $time;

        return ! isset( $existing[ $slot_key ] );
    }

    /**
     * Get blocked dates.
     *
     * @since 1.0.0
     * @return array Blocked dates.
     */
    private function get_blocked_dates(): array {
        return get_option( 'boostbot_blocked_dates', array() );
    }

    /**
     * Validate submitted data.
     *
     * @since 1.0.0
     * @param array $data Submitted data.
     * @return array Validation result.
     */
    public function validate( array $data ): array {
        $errors = array();

        $settings = $this->get_schema()['settings'];

        // Validate date
        if ( empty( $data['date'] ) ) {
            $errors['date'] = __( 'Please select a date', 'boostbot' );
        } else {
            $selected_date = DateTime::createFromFormat( 'Y-m-d', $data['date'] );
            $today = new DateTime( 'today' );

            if ( ! $selected_date || $selected_date < $today ) {
                $errors['date'] = __( 'Invalid date', 'boostbot' );
            } elseif ( ! in_array( (int) $selected_date->format( 'w' ), $settings['available_days'], true ) ) {
                // Reject days of the week that are not configured as available.
                $errors['date'] = __( 'The selected day is not available for booking', 'boostbot' );
            }
        }

        // Validate time
        if ( empty( $data['time'] ) ) {
            $errors['time'] = __( 'Please select a time', 'boostbot' );
        } elseif ( ! $this->is_time_within_availability( $data['time'], $settings ) ) {
            // Reject times outside the configured hours window or off the slot grid.
            $errors['time'] = __( 'The selected time is not available', 'boostbot' );
        } elseif ( ! empty( $data['date'] ) && ! $this->is_slot_available( $data['date'], $data['time'] ) ) {
            $errors['time'] = __( 'The selected time is not available', 'boostbot' );
        }

        // Validate contact info
        if ( empty( $data['name'] ) ) {
            $errors['name'] = __( 'Please enter your name', 'boostbot' );
        }

        if ( empty( $data['email'] ) ) {
            $errors['email'] = __( 'Please enter your email', 'boostbot' );
        } else {
            $email_error = $this->validate_email( $data['email'] );
            if ( $email_error ) {
                $errors['email'] = $email_error;
            }
        }

        return array(
            'valid'  => empty( $errors ),
            'errors' => $errors,
        );
    }

    /**
     * Process submitted data.
     *
     * @since 1.0.0
     * @param array $data    Submitted data.
     * @param array $context Submission context.
     * @return array Processing result.
     */
    public function process( array $data, array $context = array() ): array {
        // Sanitize data
        $appointment = array(
            'date'            => sanitize_text_field( $data['date'] ),
            'time'            => sanitize_text_field( $data['time'] ),
            'name'            => $this->sanitize_field( $data['name'], 'text' ),
            'email'           => $this->sanitize_field( $data['email'], 'email' ),
            'phone'           => $this->sanitize_field( $data['phone'] ?? '', 'phone' ),
            'conversation_id' => $context['conversation_id'] ?? '',
            'visitor_id'      => $context['visitor_id'] ?? '',
            'created_at'      => current_time( 'mysql' ),
            'status'          => 'pending',
        );

        // Save appointment
        $slot_key = $appointment['date'] . '_' . $appointment['time'];
        $existing = get_option( 'boostbot_appointments', array() );
        $existing[ $slot_key ] = $appointment;
        update_option( 'boostbot_appointments', $existing );

        // Create lead
        $lead_model = new BoostBot_Lead_Model();
        $lead_id = $lead_model->create( array(
            'conversation_id' => $context['conversation_id'] ?? null,
            'visitor_id'      => $context['visitor_id'] ?? null,
            'name'            => $appointment['name'],
            'email'           => $appointment['email'],
            'phone'           => $appointment['phone'],
            'lead_status'     => 'new',
            'lead_score'      => $this->calculate_lead_score( $appointment ),
            'form_data'       => array(
                'type'        => 'appointment',
                'date'        => $appointment['date'],
                'time'        => $appointment['time'],
            ),
            'notes'           => sprintf(
                /* translators: 1: Date, 2: Time */
                __( 'Appointment scheduled for %1$s at %2$s', 'boostbot' ),
                $appointment['date'],
                $appointment['time']
            ),
        ) );

        BoostBot_Logger::info( 'Appointment created', array(
            'date'    => $appointment['date'],
            'time'    => $appointment['time'],
            'lead_id' => $lead_id,
        ) );

        // Send notifications
        $this->send_confirmation_email( $appointment );
        $this->send_admin_notification(
            __( 'New appointment scheduled', 'boostbot' ),
            sprintf(
                /* translators: 1: Name, 2: Date, 3: Time, 4: Email */
                __( "New appointment scheduled:\n\nName: %1\$s\nDate: %2\$s\nTime: %3\$s\nEmail: %4\$s", 'boostbot' ),
                $appointment['name'],
                $appointment['date'],
                $appointment['time'],
                $appointment['email']
            )
        );

        // Fire hook
        $this->do_action( 'appointment_created', $appointment, $lead_id );

        return array(
            'appointment_id' => $slot_key,
            'lead_id'        => $lead_id,
            'date'           => $appointment['date'],
            'time'           => $appointment['time'],
            'message'        => $this->get_schema()['success_message'],
        );
    }

    /**
     * Send confirmation email to customer.
     *
     * @since 1.0.0
     * @param array $appointment Appointment data.
     */
    private function send_confirmation_email( array $appointment ) {
        $subject = __( 'Appointment Confirmation', 'boostbot' );

        $message = sprintf(
            /* translators: %s: Customer name */
            __( 'Hello %s,', 'boostbot' ),
            $appointment['name']
        ) . "\n\n";

        $message .= __( 'Your appointment has been confirmed!', 'boostbot' ) . "\n\n";

        $message .= sprintf(
            /* translators: %s: Date */
            __( 'Date: %s', 'boostbot' ),
            $appointment['date']
        ) . "\n";

        $message .= sprintf(
            /* translators: %s: Time */
            __( 'Time: %s', 'boostbot' ),
            $appointment['time']
        ) . "\n\n";

        $message .= __( 'We look forward to seeing you!', 'boostbot' ) . "\n";
        $message .= get_bloginfo( 'name' );

        wp_mail( $appointment['email'], $subject, $message );
    }
}
