<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Data_Objects\Chatbot_User;
use Limb_Chatbot\Includes\Data_Objects\Ip_Block;
use Limb_Chatbot\Includes\Repositories\Ip_Block_Repository;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Ip_Block_Service;
use WP_Error;
use WP_REST_Server;

/**
 * Controller for managing blocked chatbot IP addresses.
 *
 * @since 2.0.0
 */
class Ip_Blocks_Controller extends Rest_Controller {

	/**
	 * REST route base.
	 *
	 * @var string
	 */
	protected $rest_base = 'ip-blocks';

	/**
	 * Repository instance.
	 *
	 * @var Ip_Block_Repository
	 */
	protected Ip_Block_Repository $repository;

	/**
	 * Service instance.
	 *
	 * @var Ip_Block_Service
	 */
	protected Ip_Block_Service $service;

	/**
	 * Constructor.
	 *
	 * @since 2.0.0
	 */
	public function __construct() {
		$this->repository = new Ip_Block_Repository();
		$this->service    = new Ip_Block_Service( $this->repository );
	}

	/**
	 * Register API routes.
	 *
	 * @return void
	 * @since 2.0.0
	 */
	public function register_routes() {
		register_rest_route( $this->namespace, '/' . $this->rest_base, [
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => $this->get_collection_params(),
			],
			[
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_item' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => $this->get_create_args(),
			],
			[
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => [ $this, 'batch_delete' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => [
					'id' => [
						'type'              => 'array',
						'required'          => true,
						'sanitize_callback' => [ $this, 'sanitize_block_ids' ],
						'validate_callback' => [ $this, 'validate_block_ids' ],
					],
				],
			],
			'schema' => [ $this, 'get_item_schema' ],
		] );

		register_rest_route( $this->namespace, '/chatbot-users/(?P<id>[\d]+)/block', [
			[
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_item_by_user' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => $this->get_user_block_args(),
			],
		] );

		register_rest_route( $this->namespace, '/chatbot-users/(?P<id>[\d]+)/unblock', [
			[
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => [ $this, 'unblock_user' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => [
					'id' => [
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
						'validate_callback' => [ $this, 'validate_chatbot_user_id' ],
					],
				],
			],
		] );
	}

	/**
	 * Collection parameters.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function get_collection_params() {
		return array_merge( parent::get_collection_params(), [
			'orderby'         => [
				'type'              => 'string',
				'default'           => 'created_at',
				'enum'              => [ 'id', 'ip', 'location', 'reason', 'type', 'created_at', 'blocked_until' ],
				'sanitize_callback' => 'sanitize_key',
				'validate_callback' => 'rest_validate_request_arg',
			],
			'order'           => [
				'type'              => 'string',
				'default'           => 'desc',
				'enum'              => [ 'asc', 'desc' ],
				'sanitize_callback' => 'sanitize_key',
				'validate_callback' => 'rest_validate_request_arg',
			],
			'ip'              => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => [ $this, 'validate_ip' ],
			],
			'search'          => [
				'description'       => __( 'Search IP blocks by IP, reason, or location.', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'search_fields'   => [
				'description'       => __( 'Fields to search in (ip, reason, location).', 'limb-chatbot' ),
				'type'              => 'array',
				'default'           => [ 'ip', 'reason', 'location' ],
				'sanitize_callback' => function ( $value ) {
					if ( ! is_array( $value ) ) {
						return [];
					}

					return array_map( 'sanitize_key', $value );
				},
				'validate_callback' => function ( $value ) {
					if ( ! is_array( $value ) ) {
						return new WP_Error(
							'invalid_search_fields',
							__( 'search_fields must be an array.', 'limb-chatbot' )
						);
					}
					$allowed_fields = [ 'ip', 'reason', 'location' ];
					foreach ( $value as $field ) {
						if ( ! in_array( $field, $allowed_fields, true ) ) {
							return new WP_Error(
								'invalid_search_field',
								sprintf(
									__( 'Invalid search field: %s. Allowed fields are: %s', 'limb-chatbot' ),
									$field,
									implode( ', ', $allowed_fields )
								)
							);
						}
					}

					return true;
				},
			],
			'type'            => [
				'type'              => 'string',
				'enum'              => [ Ip_Block::TYPE_MANUAL, Ip_Block::TYPE_AUTOMATIC ],
				'sanitize_callback' => 'sanitize_key',
				'validate_callback' => 'rest_validate_request_arg',
			],
			'chatbot_user_id' => [
				'type'              => 'integer',
				'sanitize_callback' => 'absint',
				'validate_callback' => [ $this, 'validate_chatbot_user_id' ],
			],
		] );
	}

	/**
	 * Create payload args for IP-only endpoint.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	private function get_create_args(): array {
		return [
			'ip'            => [
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => [ $this, 'validate_ip' ],
			],
			'location'      => [
				'type'              => 'string',
				'required'          => false,
				'sanitize_callback' => 'sanitize_text_field',
			],
			'reason'        => [
				'type'              => 'string',
				'required'          => false,
				'sanitize_callback' => 'sanitize_textarea_field',
			],
			'type'          => [
				'type'              => 'string',
				'required'          => false,
				'default'           => Ip_Block::TYPE_MANUAL,
				'enum'              => [ Ip_Block::TYPE_MANUAL, Ip_Block::TYPE_AUTOMATIC ],
				'sanitize_callback' => 'sanitize_key',
			],
			'blocked_until' => [
				'type'              => 'string',
				'required'          => false,
				'default'           => null,
				'sanitize_callback' => [ $this, 'sanitize_blocked_until' ],
				'validate_callback' => [ $this, 'validate_blocked_until' ],
			],
		];
	}

	/**
	 * Create payload args for user-based endpoint.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	private function get_user_block_args(): array {
		return [
			'id'            => [
				'type'              => 'integer',
				'required'          => true,
				'sanitize_callback' => 'absint',
				'validate_callback' => [ $this, 'validate_chatbot_user_id' ],
			],
			'location'      => [
				'type'              => 'string',
				'required'          => false,
				'sanitize_callback' => 'sanitize_text_field',
			],
			'reason'        => [
				'type'              => 'string',
				'required'          => false,
				'sanitize_callback' => 'sanitize_textarea_field',
			],
			'type'          => [
				'type'              => 'string',
				'required'          => false,
				'default'           => Ip_Block::TYPE_MANUAL,
				'enum'              => [ Ip_Block::TYPE_MANUAL, Ip_Block::TYPE_AUTOMATIC ],
				'sanitize_callback' => 'sanitize_key',
			],
			'blocked_until' => [
				'type'              => 'string',
				'required'          => false,
				'default'           => null,
				'sanitize_callback' => [ $this, 'sanitize_blocked_until' ],
				'validate_callback' => [ $this, 'validate_blocked_until' ],
			],
		];
	}

	/**
	 * Get a collection of IP blocks.
	 *
	 * @param  \WP_REST_Request  $request  Request object.
	 *
	 * @return \WP_REST_Response|WP_Error
	 * @since 2.0.0
	 */
	public function get_items( $request ) {
		try {
			$items = $this->repository->get_items( $request->get_query_params() );

			return rest_ensure_response( $this->prepare_collection( $items, $request ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Create a block by IP.
	 *
	 * @param  \WP_REST_Request  $request  Request object.
	 *
	 * @return \WP_REST_Response|WP_Error
	 * @since 2.0.0
	 */
	public function create_item( $request ) {
		try {
			$payload = $request->get_json_params();
			$item    = $this->service->create_from_ip( is_array( $payload ) ? $payload : [] );
			if ( is_wp_error( $item ) ) {
				return $item;
			}

			return rest_ensure_response( $item );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Create a block by chatbot user.
	 *
	 * @param  \WP_REST_Request  $request  Request object.
	 *
	 * @return \WP_REST_Response|WP_Error
	 * @since 2.0.0
	 */
	public function create_item_by_user( $request ) {
		try {
			$chatbot_user_id = absint( $request->get_param( 'id' ) );

			// Prefer values coming from registered REST args (sanitized + validated).
			// Using get_json_params() here can miss fields depending on how WP parses the request body.
			$payload = [
				'type'          => $request->get_param( 'type' ),
				'location'      => $request->get_param( 'location' ),
				'reason'        => $request->get_param( 'reason' ),
				'blocked_until' => $request->get_param( 'blocked_until' ),
			];

			// Back-compat: allow clients to send `until` instead of `blocked_until`.
			// Also fallback to raw JSON when sanitization didn't populate params (e.g. missing Content-Type header).
			$json = $request->get_json_params();
			if ( empty( $json ) ) {
				$raw_body = (string) $request->get_body();
				if ( ! empty( $raw_body ) ) {
					$decoded = json_decode( $raw_body, true );
					if ( is_array( $decoded ) ) {
						$json = $decoded;
					}
				}
			}

			if ( is_array( $json ) ) {
				if ( array_key_exists( 'until', $json ) && ( null === $payload['blocked_until'] || '' === $payload['blocked_until'] ) ) {
					$payload['blocked_until'] = $json['until'];
				}

				if ( ( null === $payload['reason'] || '' === $payload['reason'] ) && array_key_exists( 'reason', $json ) ) {
					$payload['reason'] = $json['reason'];
				}

				if ( empty( $payload['type'] ) && array_key_exists( 'type', $json ) ) {
					$payload['type'] = $json['type'];
				}

				if ( null === $payload['location'] && array_key_exists( 'location', $json ) ) {
					$payload['location'] = $json['location'];
				}
			}

			// Ensure sanitization for cases where WP didn't parse body into registered REST args.
			$payload['reason'] = ( null === ( $payload['reason'] ?? null ) || '' === (string) $payload['reason'] )
				? null
				: sanitize_textarea_field( (string) $payload['reason'] );

			$payload['blocked_until'] = $this->sanitize_blocked_until( $payload['blocked_until'] ?? null );

			$blocked_until_validation = $this->validate_blocked_until( $payload['blocked_until'] );
			if ( is_wp_error( $blocked_until_validation ) ) {
				return $blocked_until_validation;
			}

			$item = $this->service->create_from_user( $chatbot_user_id, $payload );
			if ( is_wp_error( $item ) ) {
				return $item;
			}

			return rest_ensure_response( $item );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Batch delete (unblock) IP blocks by IDs.
	 *
	 * @param  \WP_REST_Request  $request  Request object.
	 *
	 * @return \WP_REST_Response|WP_Error
	 * @since 2.0.0
	 */
	public function batch_delete( $request ) {
		try {
			$ids     = (array) $request->get_param( 'id' );
			$deleted = $this->repository->batch_delete( $ids );

			return rest_ensure_response( [ 'deleted' => $deleted ] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Unblock all entries related to a chatbot user.
	 *
	 * @param  \WP_REST_Request  $request  Request object.
	 *
	 * @return \WP_REST_Response|WP_Error
	 * @since 2.0.0
	 */
	public function unblock_user( $request ) {
		try {
			$result = $this->service->unblock_user( absint( $request->get_param( 'id' ) ) );
			if ( is_wp_error( $result ) ) {
				return $result;
			}

			return rest_ensure_response( [ 'deleted_count' => (int) $result ] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Sanitize block IDs payload.
	 *
	 * @param  mixed  $value  IDs payload.
	 *
	 * @return int[]
	 * @since 2.0.0
	 */
	public function sanitize_block_ids( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$ids = array_map( 'absint', $value );
		$ids = array_filter( array_unique( $ids ) );

		return array_values( $ids );
	}

	/**
	 * Validate block IDs payload.
	 *
	 * @param  mixed  $value  IDs payload.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_block_ids( $value ): bool {
		if ( ! is_array( $value ) || empty( $value ) ) {
			return false;
		}

		foreach ( $value as $id ) {
			if ( ! is_numeric( $id ) || $id <= 0 ) {
				return false;
			}
		}

		return Ip_Block::count( [ 'id' => $value ] ) === count( $value );
	}

	/**
	 * Validate IP argument.
	 *
	 * @param  mixed  $value  Input value.
	 *
	 * @return bool|WP_Error
	 * @since 2.0.0
	 */
	public function validate_ip( $value ) {
		$ip = sanitize_text_field( (string) $value );
		if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			return new WP_Error( 'invalid_ip', __( 'Invalid IP address.', 'limb-chatbot' ) );
		}

		return true;
	}

	/**
	 * Validate chatbot user ID.
	 *
	 * @param  mixed  $value  User id.
	 *
	 * @return bool|WP_Error
	 * @since 2.0.0
	 */
	public function validate_chatbot_user_id( $value ) {
		$user_id = absint( $value );
		if ( $user_id < 1 || ! Chatbot_User::find( $user_id ) ) {
			return new WP_Error( 'invalid_chatbot_user_id', __( 'Chatbot user not found.', 'limb-chatbot' ) );
		}

		return true;
	}

	/**
	 * Sanitize blocked_until value.
	 *
	 * @param  mixed  $value  Raw input value.
	 *
	 * @return string|null
	 * @since 2.0.0
	 */
	public function sanitize_blocked_until( $value ): ?string {
		if ( null === $value || '' === $value ) {
			return null;
		}

		return sanitize_text_field( (string) $value );
	}

	/**
	 * Validate blocked_until datetime.
	 *
	 * @param  mixed  $value  Datetime value.
	 *
	 * @return bool|WP_Error
	 * @since 2.0.0
	 */
	public function validate_blocked_until( $value ) {
		if ( null === $value || '' === $value ) {
			return true;
		}

		if ( false === strtotime( (string) $value ) ) {
			return new WP_Error(
				'invalid_blocked_until',
				__( 'Invalid blocked_until format. Use a valid datetime string.', 'limb-chatbot' )
			);
		}

		return true;
	}

	/**
	 * JSON schema for IP block resource.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->schema;
		}

		$this->schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'ip_block',
			'type'       => 'object',
			'properties' => [
				'id'              => [
					'description' => __( 'Unique identifier for the IP block row.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'ip'              => [
					'description' => __( 'Blocked IP address.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'location'        => [
					'description' => __( 'IP location string.', 'limb-chatbot' ),
					'type'        => [ 'string', 'null' ],
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'reason'          => [
					'description' => __( 'Optional block reason.', 'limb-chatbot' ),
					'type'        => [ 'string', 'null' ],
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'type'            => [
					'description' => __( 'Block type.', 'limb-chatbot' ),
					'type'        => 'string',
					'enum'        => [ Ip_Block::TYPE_MANUAL, Ip_Block::TYPE_AUTOMATIC ],
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'created_at'      => [
					'description' => __( 'When the IP was blocked.', 'limb-chatbot' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'blocked_until'   => [
					'description' => __( 'Expiration datetime; null means permanent block.', 'limb-chatbot' ),
					'type'        => [ 'string', 'null' ],
					'format'      => 'date-time',
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
				'chatbot_user_id' => [
					'description' => __( 'Related chatbot user ID if available.', 'limb-chatbot' ),
					'type'        => [ 'integer', 'null' ],
					'context'     => [ 'view', 'edit' ],
					'readonly'    => true,
				],
			],
		];

		return $this->schema;
	}
}
