<?php
namespace Bookaro\Controller\Rest;

defined( 'ABSPATH' ) || exit;

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

class ProviderSettingsController extends BaseController
{
    private ScheduleService $schedule;

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

    public function register_routes(): void
    {
        register_rest_route( $this->namespace, '/provider-settings', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_provider_settings' ],
                'permission_callback' => fn() : bool => bookaro_user_has_provider_access(),
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'update_provider_settings' ],
                'permission_callback' => [ $this, 'can_update_provider_settings' ],
                'args'                => [
                    'availability'       => [ 'required' => false ],
                    'weekly'             => [ 'type' => 'array', 'required' => false, 'default' => [] ],
                    'timeslots'          => [ 'type' => 'array', 'required' => false, 'default' => [] ],
                    // Bug 25: `'type' => 'array'` makes WP's REST schema
                    // validator reject associative arrays (rest_invalid_type)
                    // — even though ScheduleService::normalize_exceptions_payload
                    // explicitly accepts BOTH a list of dates (block-only) and
                    // a dict mapping date => is_open. The sibling AJAX endpoint
                    // already accepts both shapes by json_decode + branching.
                    // Drop the schema type so callers can send either; the
                    // normalizer handles shape detection (is_list_array) and
                    // discards invalid entries.
                    'exceptions'         => [ 'required' => false, 'default' => [] ],
                    'slots_per_interval' => [ 'required' => false ],
                ],
            ],
        ] );
    }

    public function can_update_provider_settings( WP_REST_Request $req ): bool|\WP_Error
    {
        $auth = $this->require_authenticated_write(
            $req,
            __( 'You must be logged in to update settings.', 'mybookaro' )
        );
        if ( is_wp_error( $auth ) ) {
            return $auth;
        }

        if ( ! bookaro_user_has_provider_access() ) {
            return new \WP_Error(
                'bookaro_forbidden',
                __( 'Permission denied.', 'mybookaro' ),
                [ 'status' => 403 ]
            );
        }

        return true;
    }

    public function get_provider_settings( WP_REST_Request $req ): WP_REST_Response
    {
        $provider_id = get_current_user_id();
        $payload     = $this->schedule->get_provider_settings_payload( $provider_id );

        return rest_ensure_response( $payload );
    }

    public function update_provider_settings( WP_REST_Request $req ): WP_REST_Response
    {
        $provider_id = get_current_user_id();
        $body        = (array) $req->get_json_params();

        if ( is_array( $body['availability'] ?? null ) ) {
            $normalized = $this->schedule->normalize_availability_payload( $body['availability'] );
        } else {
            $normalized = [
                'weekly'             => $this->schedule->normalize_weekly_flags( (array) ( $body['weekly'] ?? [] ) ),
                'timeslots'          => $this->schedule->normalize_timeslots_payload( (array) ( $body['timeslots'] ?? [] ) ),
                'slots_per_interval' => $this->schedule->normalize_slots_per_interval(
                    $body['slots_per_interval'] ?? $this->schedule->get_slots_per_interval( $provider_id )
                ),
            ];
        }

        $exceptions = $this->schedule->normalize_exceptions_payload( (array) ( $body['exceptions'] ?? [] ) );

        // Bug 61: detect conflicting bookings before applying the
        // exceptions. set_exceptions() blindly deletes and re-inserts the
        // rows, so an approved/pending booking on a date the provider is
        // closing becomes a ghost: the booking row keeps status='approved'
        // but the calendar treats the day as closed. Provider sees no
        // warning, customer dashboard still shows the booking, customer
        // turns up to a closed business. Surface the conflict list in the
        // response so the UI (and any third-party integration) can warn
        // the provider — they may still want to close the day, but they
        // need to know which bookings to cancel/reschedule first.
        $closed_dates = [];
        foreach ( $exceptions as $key => $row ) {
            $iso = '';
            $is_open = null;
            if ( is_array( $row ) ) {
                $iso = isset( $row['date'] ) ? sanitize_text_field( (string) $row['date'] ) : '';
                if ( '' === $iso ) {
                    $iso = is_string( $key ) ? sanitize_text_field( $key ) : '';
                }
                if ( array_key_exists( 'is_open', $row ) ) {
                    $is_open = (int) $row['is_open'];
                } elseif ( array_key_exists( 'closed', $row ) ) {
                    $is_open = $row['closed'] ? 0 : 1;
                }
            } elseif ( is_string( $key ) ) {
                $iso = sanitize_text_field( $key );
                $is_open = is_numeric( $row ) ? (int) $row : null;
            }
            if ( '' === $iso || 0 !== $is_open ) {
                continue;
            }
            if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $iso ) === 1 ) {
                $closed_dates[ $iso ] = true;
            }
        }

        $conflicts = [];
        if ( ! empty( $closed_dates ) ) {
            $dates_list = array_keys( $closed_dates );
            $repo = new BookingRepository();
            $table = esc_sql( $repo->get_table_name() );
            $placeholders = implode( ',', array_fill( 0, count( $dates_list ), '%s' ) );
            global $wpdb;
            $params = array_merge( [ $provider_id ], $dates_list );
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $sql = "SELECT id, booking_date, booking_date_to, status, service_label
                    FROM `{$table}`
                    WHERE provider_id = %d
                      AND status IN ('pending','approved')
                      AND (
                          DATE(booking_date) IN ($placeholders)
                          OR DATE(booking_date_to) IN ($placeholders)
                      )";
            $params_extended = array_merge( $params, $dates_list );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- table name is esc_sql()'d, values are bound via $wpdb->prepare().
            $rows = $wpdb->get_results( $wpdb->prepare( $sql, $params_extended ), ARRAY_A );
            foreach ( (array) $rows as $row ) {
                $start = (string) ( $row['booking_date'] ?? '' );
                $end   = (string) ( $row['booking_date_to'] ?? '' );
                $start_date = substr( $start, 0, 10 );
                $end_date   = '' !== $end ? substr( $end, 0, 10 ) : $start_date;
                $hits = [];
                foreach ( $dates_list as $iso ) {
                    if ( $iso >= $start_date && $iso <= $end_date ) {
                        $hits[] = $iso;
                    }
                }
                $conflicts[] = [
                    'id'              => (int) $row['id'],
                    'service_label'   => (string) ( $row['service_label'] ?? '' ),
                    'status'          => (string) ( $row['status'] ?? '' ),
                    'booking_date'    => $start,
                    'booking_date_to' => $end,
                    'closed_dates'    => $hits,
                ];
            }
        }

        $this->schedule->set_weekly_availability( $provider_id, $normalized['weekly'] );
        $this->schedule->set_timeslots( $provider_id, $normalized['timeslots'] );
        $this->schedule->set_slots_per_interval( $provider_id, $normalized['slots_per_interval'] );
        $this->schedule->set_exceptions( $provider_id, $exceptions );

        return rest_ensure_response( [
            'success'             => true,
            'exception_conflicts' => $conflicts,
        ] );
    }
}
