<?php
namespace Bookaro\Controller\Rest;

defined( 'ABSPATH' ) || exit;

use Bookaro\Service\ScheduleService;
use Bookaro\Repository\BookingRepository;
use function bookaro_get_user_meta;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

class AvailabilityController extends BaseController
{
    private ScheduleService $schedule;
    private BookingRepository $db;

    public function __construct( ScheduleService $schedule, BookingRepository $db )
    {
        $this->schedule = $schedule;
        $this->db       = $db;
    }

    public function register_routes(): void
    {
        register_rest_route( $this->namespace, '/provider/(?P<id>\d+)/availability', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_availability' ],
                'permission_callback' => [ $this, 'can_view_availability' ],
                'args'                => [
                    'listing_id' => [
                        'required'          => false,
                        'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v >= 0,
                    ],
                    'from' => [
                        'required'          => true,
                        'validate_callback' => fn( $v ) => preg_match( '/^\d{4}-\d{2}-\d{2}$/', $v ) === 1,
                    ],
                    'to'   => [
                        'required'          => true,
                        'validate_callback' => fn( $v ) => preg_match( '/^\d{4}-\d{2}-\d{2}$/', $v ) === 1,
                    ],
                    'duration' => [
                        'required'          => false,
                        'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v >= 0,
                    ],
                    'exclude_booking_id' => [
                        'required'          => false,
                        'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v >= 0,
                    ],
                ],
            ],
        ] );
    }

    public function can_view_availability( WP_REST_Request $req )
    {
        // Bug 53/54 family (availability layer): when the site allows guest
        // bookings (require_login=0), anonymous visitors must be able to read
        // availability to pick a slot — otherwise the booking modal opens to an
        // empty, all-disabled calendar. Listing/provider validation below still
        // applies, and bookaro_is_allowed_listing() keeps it to supported
        // published listings, so this exposes only bookable open/closed slots.
        $require_login = ! function_exists( 'bookaro_get_option' )
            || (bool) bookaro_get_option( 'require_login', 1 );
        if ( $require_login && ! bookaro_user_has_dashboard_access() ) {
            return new \WP_Error(
                'bookaro_forbidden',
                __( 'Permission denied.', 'mybookaro' ),
                [ 'status' => 403 ]
            );
        }

        $listing_id = absint( $req->get_param( 'listing_id' ) );
        $provider_id = (int) $req->get_param( 'id' );
        if ( $listing_id < 1 ) {
            $single_business = function_exists( 'bookaro_is_single_business_site' )
                && bookaro_is_single_business_site();
            if ( ! $single_business ) {
                return new \WP_Error(
                    'bookaro_invalid_listing',
                    __( 'Listing is required for this availability request.', 'mybookaro' ),
                    [ 'status' => 400 ]
                );
            }
            if ( $provider_id < 1 || ! get_user_by( 'ID', $provider_id ) ) {
                return new \WP_Error(
                    'bookaro_invalid_provider',
                    __( 'Invalid provider.', 'mybookaro' ),
                    [ 'status' => 400 ]
                );
            }
            if ( bookaro_user_has_provider_access() && ! current_user_can( 'manage_options' ) ) {
                if ( get_current_user_id() !== $provider_id ) {
                    return new \WP_Error(
                        'bookaro_forbidden',
                        __( 'You do not have permission to access this provider availability.', 'mybookaro' ),
                        [ 'status' => 403 ]
                    );
                }
            }
            return true;
        }

        if ( ! bookaro_is_allowed_listing( $listing_id ) ) {
            return new \WP_Error(
                'bookaro_invalid_listing',
                __( 'Listing not supported.', 'mybookaro' ),
                [ 'status' => 400 ]
            );
        }
        if ( function_exists( 'bookaro_is_lite_listing_allowed' ) && ! bookaro_is_lite_listing_allowed( $listing_id ) ) {
            return new \WP_Error(
                'bookaro_invalid_listing',
                __( 'Listing not supported.', 'mybookaro' ),
                [ 'status' => 403 ]
            );
        }
        // Bug 51: BookingService::createBooking rejects bookings against
        // unpublished listings ('bookaro_listing_unpublished'), but the
        // availability endpoint happily handed out a full day of slots
        // first. Visitors walked all the way through the calendar -> time
        // slot -> contact form before learning the listing was a draft.
        // Mirror the publish-status gate here so the modal can fail fast
        // (no slots, clear error) instead of leading the customer on.
        $listing_post = get_post( $listing_id );
        if ( ! $listing_post || 'publish' !== $listing_post->post_status ) {
            return new \WP_Error(
                'bookaro_listing_unpublished',
                __( 'Listing is not published.', 'mybookaro' ),
                [ 'status' => 400 ]
            );
        }

        $owner_id    = bookaro_get_listing_owner_id( $listing_id );
        if ( $owner_id < 1 || $owner_id !== $provider_id ) {
            return new \WP_Error(
                'bookaro_listing_owner_mismatch',
                __( 'Listing does not belong to this provider.', 'mybookaro' ),
                [ 'status' => 403 ]
            );
        }

        if ( bookaro_user_has_provider_access() && ! current_user_can( 'manage_options' ) ) {
            $current_user_id = get_current_user_id();
            if ( $owner_id !== $current_user_id ) {
                return new \WP_Error(
                    'bookaro_forbidden',
                    __( 'You do not have permission to access this listing.', 'mybookaro' ),
                    [ 'status' => 403 ]
                );
            }
        }

        return true;
    }

    public function get_availability( WP_REST_Request $req ): WP_REST_Response|\WP_Error
    {
        $provider_id = (int) $req->get_param( 'id' );
        $from        = $req->get_param( 'from' );
        $to          = $req->get_param( 'to' );
        $duration    = absint( $req->get_param( 'duration' ) );
        $exclude_booking_id = absint( $req->get_param( 'exclude_booking_id' ) );
        $availability_id = $provider_id;

        // Bug 31: cap the requested date range. compute_gaps iterates day-by-day
        // over (to - from), instantiating DateTime objects and stepping through
        // every timeslot template. With pathological provider settings
        // (1-minute interval, multiple timeslots per weekday) a 100-year query
        // can pin a worker for seconds. Cap at the same DEFAULT_RANGE_MAX_DAYS
        // (370 — slightly over a year) that the exceptions-range endpoint
        // already uses, with the same filter so a single hook covers both.
        $range_check = $this->require_range_within_limit( $from, $to );
        if ( $range_check instanceof \WP_Error ) {
            return $range_check;
        }

        $weekly      = $this->schedule->get_weekly_availability( $availability_id );
        $timeslots   = $this->schedule->get_timeslots(        $availability_id );
        $exceptions  = $this->schedule->get_exceptions(       $availability_id );
        $bookedSlots = $this->db->get_booked_slots(           $provider_id, $from, $to, $exclude_booking_id );

        $rawSlots = $this->db->get_available_slots( $provider_id, $from, $to, $duration, $exclude_booking_id );
        $available = [];
        foreach ( $rawSlots as $s ) {
            $date = substr( $s['start'], 0, 10 );
            $time = substr( $s['start'], 11, 5 );
            $available[ $date ][] = $time;
        }

        $slots = (int) bookaro_get_user_meta( $availability_id, 'bookaro_slots_per_interval', 0 );
        if ( $slots < 1 ) {
            $slots = 1;
        }

        return rest_ensure_response( [
            'weekly'      => $weekly,
            'timeslots'   => $timeslots,
            'exceptions'  => $exceptions,
            'bookedSlots' => $bookedSlots,
            'available'   => $available,
            'slots'       => $slots,
        ] );
    }
}
