<?php

namespace Limb_Chatbot\Includes\Services;

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 WP_Error;

/**
 * Domain service for creating and checking IP blocks.
 *
 * @since 2.0.0
 */
class Ip_Block_Service {

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

	/**
	 * Constructor.
	 *
	 * @param  Ip_Block_Repository|null  $repository  Optional repository.
	 * @since 2.0.0
	 */
	public function __construct( ?Ip_Block_Repository $repository = null ) {
		$this->repository = $repository ?? new Ip_Block_Repository();
	}

	/**
	 * Create block entry by chatbot user ID.
	 *
	 * @param  int  $chatbot_user_id  Chatbot user ID.
	 * @param  array  $data  Additional block payload.
	 *
	 * @return Ip_Block|WP_Error
	 * @since 2.0.0
	 */
	public function create_from_user( int $chatbot_user_id, array $data = [] ) {
		$user = Chatbot_User::find( $chatbot_user_id );
		if ( ! $user instanceof Chatbot_User ) {
			return new WP_Error( 'chatbot_user_not_found', __( 'Chatbot user not found.', 'limb-chatbot' ) );
		}

		$ip = $user->get_ip();
		if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			return new WP_Error( 'invalid_ip', __( 'User has no valid IP address.', 'limb-chatbot' ) );
		}

		$location = ! empty( $data['location'] )
			? sanitize_text_field( $data['location'] )
			: $this->resolve_location_from_user( $user );

		return $this->repository->create( [
			'ip'              => $ip,
			'chatbot_user_id' => $chatbot_user_id,
			'location'        => $location,
			'reason'          => isset( $data['reason'] ) ? sanitize_textarea_field( $data['reason'] ) : null,
			'type'            => $this->sanitize_type( $data['type'] ?? Ip_Block::TYPE_MANUAL ),
			'created_at'      => current_time( 'mysql' ),
			'blocked_until'   => $this->normalize_blocked_until( $data['blocked_until'] ?? null ),
		] );
	}

	/**
	 * Create block entry by IP only.
	 *
	 * @param  array  $data  Block payload.
	 *
	 * @return Ip_Block|WP_Error
	 * @since 2.0.0
	 */
	public function create_from_ip( array $data ) {
		$ip = isset( $data['ip'] ) ? sanitize_text_field( $data['ip'] ) : '';
		if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			return new WP_Error( 'invalid_ip', __( 'A valid IP address is required.', 'limb-chatbot' ) );
		}

		$related_user = Chatbot_User::where( [ 'ip' => $ip ], 1, 1, 'id', 'DESC' )->first();

		$location = ! empty( $data['location'] )
			? sanitize_text_field( $data['location'] )
			: $this->resolve_location_by_ip( $ip, $related_user instanceof Chatbot_User ? $related_user : null );

		return $this->repository->create( [
			'ip'              => $ip,
			'chatbot_user_id' => null,
			'location'        => $location,
			'reason'          => isset( $data['reason'] ) ? sanitize_textarea_field( $data['reason'] ) : null,
			'type'            => $this->sanitize_type( $data['type'] ?? Ip_Block::TYPE_MANUAL ),
			'created_at'      => current_time( 'mysql' ),
			'blocked_until'   => $this->normalize_blocked_until( $data['blocked_until'] ?? null ),
		] );
	}

	/**
	 * Returns true when current chatbot user has an active block.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function is_current_user_blocked_now(): bool {
		$current_user = User_Manager::instance()->get_current_user();
		if ( ! $current_user instanceof Chatbot_User ) {
			return false;
		}

		$ip = $current_user->get_ip();
		if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			return false;
		}

		return $this->repository->is_blocked_now( $ip, $current_user->get_id() );
	}

	/**
	 * Unblock all entries related to a chatbot user.
	 *
	 * Removes records linked by chatbot_user_id and by user's current IP.
	 *
	 * @param  int  $chatbot_user_id  Chatbot user ID.
	 *
	 * @return int|WP_Error Number of deleted rows or WP_Error on failure.
	 * @since 2.0.0
	 */
	public function unblock_user( int $chatbot_user_id ) {
		$user = Chatbot_User::find( $chatbot_user_id );
		if ( ! $user instanceof Chatbot_User ) {
			return new WP_Error( 'chatbot_user_not_found', __( 'Chatbot user not found.', 'limb-chatbot' ) );
		}

		$ip = $user->get_ip();
		$ip = ( ! empty( $ip ) && filter_var( $ip, FILTER_VALIDATE_IP ) ) ? $ip : null;

		return $this->repository->unblock_user( $chatbot_user_id, $ip );
	}

	/**
	 * Normalize blocked_until to mysql datetime or null.
	 *
	 * @param  mixed  $blocked_until  Raw value.
	 *
	 * @return string|null
	 * @since 2.0.0
	 */
	private function normalize_blocked_until( $blocked_until ): ?string {
		if ( empty( $blocked_until ) ) {
			return null;
		}

		$blocked_until = sanitize_text_field( (string) $blocked_until );
		$timestamp     = strtotime( $blocked_until );

		if ( false === $timestamp ) {
			return null;
		}

		return gmdate( 'Y-m-d H:i:s', $timestamp );
	}

	/**
	 * Resolve location from chatbot user data.
	 *
	 * @param  Chatbot_User  $user  Chatbot user.
	 *
	 * @return string|null
	 * @since 2.0.0
	 */
	private function resolve_location_from_user( Chatbot_User $user ): ?string {
		$geolocation = $user->geolocation();
		if ( ! is_array( $geolocation ) ) {
			return null;
		}

		return $this->format_location( $geolocation );
	}

	/**
	 * Resolve location by IP with user-first strategy.
	 *
	 * @param  string  $ip  IP address.
	 * @param  Chatbot_User|null  $user  Optional related user.
	 *
	 * @return string|null
	 * @since 2.0.0
	 */
	private function resolve_location_by_ip( string $ip, ?Chatbot_User $user = null ): ?string {
		if ( $user instanceof Chatbot_User ) {
			$from_user = $this->resolve_location_from_user( $user );
			if ( ! empty( $from_user ) ) {
				return $from_user;
			}
		}

		$geolocation = $this->fetch_geolocation_from_ip( $ip );
		if ( empty( $geolocation ) ) {
			return null;
		}

		return $this->format_location( $geolocation );
	}

	/**
	 * Build human-readable location string.
	 *
	 * @param  array  $geolocation  Geolocation data.
	 *
	 * @return string|null
	 * @since 2.0.0
	 */
	private function format_location( array $geolocation ): ?string {
		$parts = array_filter( [
			$geolocation['city'] ?? null,
			$geolocation['state'] ?? null,
			$geolocation['country'] ?? null,
		] );

		if ( empty( $parts ) ) {
			return null;
		}

		return sanitize_text_field( implode( ', ', $parts ) );
	}

	/**
	 * Fetch geolocation data from IP address.
	 *
	 * @param  string  $ip_address  IP address.
	 *
	 * @return array|null
	 * @since 2.0.0
	 */
	private function fetch_geolocation_from_ip( string $ip_address ): ?array {
		if ( ! filter_var( $ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
			return null;
		}

		$geolocation = $this->fetch_from_ip_api( $ip_address );
		if ( ! empty( $geolocation ) ) {
			return $geolocation;
		}

		return $this->fetch_from_ipinfo( $ip_address );
	}

	/**
	 * Fetch geolocation from ip-api.com.
	 *
	 * @param  string  $ip_address  IP address.
	 *
	 * @return array|null
	 * @since 2.0.0
	 */
	private function fetch_from_ip_api( string $ip_address ): ?array {
		$url = sprintf(
			'http://ip-api.com/json/%s?fields=status,message,country,regionName,city',
			rawurlencode( $ip_address )
		);

		$response = wp_remote_get( $url, [
			'timeout'     => 5,
			'httpversion' => '1.1',
		] );
		if ( is_wp_error( $response ) ) {
			return null;
		}

		$data = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( empty( $data ) || ( isset( $data['status'] ) && 'success' !== $data['status'] ) ) {
			return null;
		}

		return [
			'country' => $data['country'] ?? null,
			'state'   => $data['regionName'] ?? null,
			'city'    => $data['city'] ?? null,
		];
	}

	/**
	 * Fetch geolocation from ipinfo.io.
	 *
	 * @param  string  $ip_address  IP address.
	 *
	 * @return array|null
	 * @since 2.0.0
	 */
	private function fetch_from_ipinfo( string $ip_address ): ?array {
		$url = sprintf( 'https://ipinfo.io/%s/json', rawurlencode( $ip_address ) );

		$response = wp_remote_get( $url, [
			'timeout'     => 5,
			'httpversion' => '1.1',
		] );
		if ( is_wp_error( $response ) ) {
			return null;
		}

		$data = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( empty( $data ) || isset( $data['error'] ) ) {
			return null;
		}

		return [
			'country' => $data['country'] ?? null,
			'state'   => $data['region'] ?? null,
			'city'    => $data['city'] ?? null,
		];
	}

	/**
	 * Sanitize and normalize block type.
	 *
	 * @param  mixed  $type  Raw type value.
	 *
	 * @return string
	 * @since 2.0.0
	 */
	private function sanitize_type( $type ): string {
		$type = sanitize_key( (string) $type );
		if ( ! in_array( $type, [ Ip_Block::TYPE_MANUAL, Ip_Block::TYPE_AUTOMATIC ], true ) ) {
			return Ip_Block::TYPE_MANUAL;
		}

		return $type;
	}
}
