<?php

namespace Limb_Chatbot\Includes\Repositories;

use Limb_Chatbot\Includes\Data_Objects\Ip_Block;
use Limb_Chatbot\Includes\Services\Data_Object_Collection;

/**
 * Repository for IP block persistence and querying.
 *
 * @since 2.0.0
 */
class Ip_Block_Repository {

	/**
	 * Create a new IP block row.
	 *
	 * @param  array  $data  Row data.
	 *
	 * @return Ip_Block
	 * @throws \Exception
	 * @since 2.0.0
	 */
	public function create( array $data ): Ip_Block {
		return Ip_Block::create( $data );
	}

	/**
	 * Fetch a collection of IP blocks.
	 *
	 * @param  array  $params  Query parameters.
	 *
	 * @return Data_Object_Collection
	 * @since 2.0.0
	 */
	public function get_items( array $params = [] ): Data_Object_Collection {
		global $wpdb;

		$table_name = $wpdb->prefix . Ip_Block::TABLE_NAME;

		$where_conditions = [];
		$prepare_values   = [];

		if ( ! empty( $params['ip'] ) ) {
			$where_conditions[] = 'ip = %s';
			$prepare_values[]   = sanitize_text_field( $params['ip'] );
		}

		if ( isset( $params['chatbot_user_id'] ) && absint( $params['chatbot_user_id'] ) > 0 ) {
			$where_conditions[] = 'chatbot_user_id = %d';
			$prepare_values[]   = absint( $params['chatbot_user_id'] );
		}

		if ( ! empty( $params['type'] ) ) {
			$where_conditions[] = 'type = %s';
			$prepare_values[]   = sanitize_key( $params['type'] );
		}

		// Multi-field OR search support: ip/reason/location.
		if ( ! empty( $params['search'] ) && ! empty( $params['search_fields'] ) && is_array( $params['search_fields'] ) ) {
			$allowed_fields    = [ 'ip', 'reason', 'location' ];
			$search_term       = sanitize_text_field( $params['search'] );
			$search_pattern    = '%' . $wpdb->esc_like( $search_term ) . '%';
			$search_conditions = [];

			foreach ( $params['search_fields'] as $field ) {
				$field = sanitize_key( (string) $field );
				if ( in_array( $field, $allowed_fields, true ) ) {
					$search_conditions[] = "{$field} LIKE %s";
					$prepare_values[]    = $search_pattern;
				}
			}

			if ( ! empty( $search_conditions ) ) {
				$where_conditions[] = '(' . implode( ' OR ', $search_conditions ) . ')';
			}
		}

		$where_clause = ! empty( $where_conditions ) ? 'WHERE ' . implode( ' AND ', $where_conditions ) : '';

		$order_by         = sanitize_key( $params['orderby'] ?? 'created_at' );
		$allowed_order_by = [ 'id', 'ip', 'location', 'reason', 'type', 'created_at', 'blocked_until' ];
		if ( ! in_array( $order_by, $allowed_order_by, true ) ) {
			$order_by = 'created_at';
		}

		$order = strtoupper( sanitize_key( $params['order'] ?? 'DESC' ) );
		if ( ! in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
			$order = 'DESC';
		}

		$per_page = isset( $params['per_page'] ) ? absint( $params['per_page'] ) : 10;
		$page     = isset( $params['page'] ) ? absint( $params['page'] ) : 1;
		if ( $page < 1 ) {
			$page = 1;
		}
		if ( $per_page < 1 ) {
			$per_page = 10;
		}
		$offset = ( $page - 1 ) * $per_page;

		$data_sql = "SELECT *
			FROM {$table_name}
			{$where_clause}
			ORDER BY {$order_by} {$order}
			LIMIT %d, %d";
		$data_prepare_values   = $prepare_values;
		$data_prepare_values[] = $offset;
		$data_prepare_values[] = $per_page;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results(
			$wpdb->prepare( $data_sql, $data_prepare_values ),
			ARRAY_A
		);

		$items = [];
		if ( ! empty( $rows ) ) {
			foreach ( $rows as $row ) {
				$item = Ip_Block::make( $row );
				if ( $item instanceof Ip_Block ) {
					$items[] = $item;
				}
			}
		}

		$collection = new Data_Object_Collection( $items );

		$count_sql = "SELECT COUNT(*)
			FROM {$table_name}
			{$where_clause}";
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$total = empty( $prepare_values )
			? (int) $wpdb->get_var( $count_sql )
			: (int) $wpdb->get_var( $wpdb->prepare( $count_sql, $prepare_values ) );
		$collection->set_total( $total );

		return $collection;
	}

	/**
	 * Check whether an IP (or optional chatbot user) is actively blocked.
	 *
	 * @param  string  $ip  IP address to check.
	 * @param  int|null  $chatbot_user_id  Optional chatbot user ID.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function is_blocked_now( string $ip, ?int $chatbot_user_id = null ): bool {
		global $wpdb;

		$table_name = $wpdb->prefix . Ip_Block::TABLE_NAME;
		$now        = current_time( 'mysql' );

		$where_sql      = 'ip = %s';
		$prepare_values = [ $ip ];
		if ( ! empty( $chatbot_user_id ) ) {
			$where_sql      = '(ip = %s OR chatbot_user_id = %d)';
			$prepare_values = [ $ip, $chatbot_user_id ];
		}

		$sql = "SELECT id
			FROM {$table_name}
			WHERE {$where_sql}
				AND (blocked_until IS NULL OR blocked_until > %s)
			LIMIT 1";

		$prepare_values[] = $now;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->get_var( $wpdb->prepare( $sql, $prepare_values ) );

		return ! empty( $result );
	}

	/**
	 * Batch delete IP blocks by IDs.
	 *
	 * @param  int[]  $ids  Block IDs.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function batch_delete( array $ids ): bool {
		$ids = array_values( array_filter( array_map( 'absint', $ids ) ) );
		if ( empty( $ids ) ) {
			return false;
		}

		return (bool) Ip_Block::delete( [ 'id' => $ids ] );
	}

	/**
	 * Remove all blocks for a chatbot user and its current IP.
	 *
	 * @param  int  $chatbot_user_id  Chatbot user ID.
	 * @param  string|null  $ip  Optional user IP.
	 *
	 * @return int Number of deleted rows.
	 * @since 2.0.0
	 */
	public function unblock_user( int $chatbot_user_id, ?string $ip = null ): int {
		global $wpdb;

		$table_name     = $wpdb->prefix . Ip_Block::TABLE_NAME;
		$where_sql      = 'chatbot_user_id = %d';
		$prepare_values = [ $chatbot_user_id ];

		if ( ! empty( $ip ) ) {
			$where_sql      = '(chatbot_user_id = %d OR ip = %s)';
			$prepare_values = [ $chatbot_user_id, $ip ];
		}

		$sql = "DELETE FROM {$table_name} WHERE {$where_sql}";

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->query( $wpdb->prepare( $sql, $prepare_values ) );

		return false === $result ? 0 : (int) $result;
	}
}
