<?php
/**
 * User repository.
 *
 * @package Ugcify
 */

namespace Ugcify\Database;

// Capabilities is part of the Influencer module and may not be present in the Free build.
// All references use class_exists() guards with inline string fallbacks.

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class UserRepository
 */
class UserRepository {
	/**
	 * Legacy creator profile blob meta key.
	 */
	public const META_PROFILE = '_ugcify_influencer_profile';

	/**
	 * Creator bio meta key.
	 */
	public const META_BIO = '_ugcify_influencer_bio';

	/**
	 * Creator avatar meta key.
	 */
	public const META_AVATAR = '_ugcify_influencer_avatar';

	/**
	 * Creator website meta key.
	 */
	public const META_WEBSITE = '_ugcify_influencer_website';

	/**
	 * Creator Instagram handle meta key.
	 */
	public const META_SOCIAL_INSTAGRAM = '_ugcify_influencer_social_instagram';

	/**
	 * Creator TikTok handle meta key.
	 */
	public const META_SOCIAL_TIKTOK = '_ugcify_influencer_social_tiktok';

	/**
	 * Creator YouTube handle meta key.
	 */
	public const META_SOCIAL_YOUTUBE = '_ugcify_influencer_social_youtube';

	/**
	 * Creator content types meta key.
	 */
	public const META_CONTENT_TYPES = '_ugcify_influencer_content_types';

	/**
	 * Creator joined-at timestamp meta key.
	 */
	public const META_JOINED_AT = '_ugcify_influencer_joined_at';

	/**
	 * Creator lifecycle status meta key.
	 */
	public const META_STATUS = '_ugcify_influencer_status';

	/**
	 * Creator invitation meta key.
	 */
	public const META_INVITATION_ID = '_ugcify_influencer_invite_id';

	/**
	 * Legacy creator invitation meta key.
	 */
	private const META_INVITATION_ID_LEGACY = '_ugcify_influencer_invitation_id';

	/**
	 * Rights agreed boolean meta key.
	 */
	public const META_RIGHTS_AGREED = '_ugcify_influencer_rights_agreed';

	/**
	 * Accepted rights version meta key.
	 */
	public const META_RIGHTS_VERSION = '_ugcify_influencer_rights_version';

	/**
	 * Rights accepted at meta key.
	 */
	public const META_RIGHTS_ACCEPTED_AT = '_ugcify_influencer_rights_agreed_at';

	/**
	 * Legacy rights accepted at meta key.
	 */
	private const META_RIGHTS_ACCEPTED_AT_LEGACY = '_ugcify_influencer_rights_accepted_at';

	/**
	 * Rights accepted IP meta key.
	 */
	public const META_RIGHTS_ACCEPTED_IP = '_ugcify_influencer_rights_ip';

	/**
	 * Legacy rights accepted IP meta key.
	 */
	private const META_RIGHTS_ACCEPTED_IP_LEGACY = '_ugcify_influencer_rights_accepted_ip';

	/**
	 * Last portal login meta key.
	 */
	public const META_LAST_PORTAL_LOGIN = '_ugcify_influencer_last_portal_login';

	/**
	 * Get a user object by ID.
	 *
	 * @param int $user_id User ID.
	 * @return \WP_User|null
	 */
	public function get_user( int $user_id ) {
		$user = get_user_by( 'id', $user_id );

		return $user instanceof \WP_User ? $user : null;
	}

	/**
	 * Get a user object by email.
	 *
	 * @param string $email User email.
	 * @return \WP_User|null
	 */
	public function get_user_by_email( string $email ) {
		$email = sanitize_email( $email );

		if ( '' === $email ) {
			return null;
		}

		$user = get_user_by( 'email', $email );

		return $user instanceof \WP_User ? $user : null;
	}

	/**
	 * Determine whether the user is a creator.
	 *
	 * @param int $user_id User ID.
	 * @return bool
	 */
	public function is_creator( int $user_id ) {
		if ( ! class_exists( '\\Ugcify\\Influencer\\Capabilities' ) ) {
			return false;
		}

		$user = $this->get_user( $user_id );

		if ( ! $user ) {
			return false;
		}

		return in_array( \Ugcify\Influencer\Capabilities::ROLE, (array) $user->roles, true )
			|| user_can( $user, \Ugcify\Influencer\Capabilities::CAP_VIEW_PORTAL );
	}

	/**
	 * Mark a user as a creator and persist the status.
	 *
	 * @param int    $user_id User ID.
	 * @param string $status  Creator status.
	 * @return bool
	 */
	public function assign_creator_role( int $user_id, string $status = 'active' ) {
		if ( ! class_exists( '\\Ugcify\\Influencer\\Capabilities' ) ) {
			return false;
		}

		$user = $this->get_user( $user_id );

		if ( ! $user ) {
			return false;
		}

		$user->add_role( \Ugcify\Influencer\Capabilities::ROLE );
		update_user_meta( $user_id, self::META_STATUS, $this->sanitize_status( $status ) );

		return true;
	}

	/**
	 * Get creator lifecycle status.
	 *
	 * @param int $user_id User ID.
	 * @return string
	 */
	public function get_creator_status( int $user_id ) {
		$status = get_user_meta( $user_id, self::META_STATUS, true );

		if ( ! is_string( $status ) || '' === $status ) {
			return $this->is_creator( $user_id ) ? 'active' : 'inactive';
		}

		return $this->sanitize_status( $status );
	}

	/**
	 * Update creator lifecycle status.
	 *
	 * @param int    $user_id User ID.
	 * @param string $status  New status.
	 * @return bool
	 */
	public function update_creator_status( int $user_id, string $status ) {
		return false !== update_user_meta( $user_id, self::META_STATUS, $this->sanitize_status( $status ) );
	}

	/**
	 * Get creator profile data.
	 *
	 * @param int $user_id User ID.
	 * @return array<string,mixed>
	 */
	public function get_creator_profile( int $user_id ) {
		$user = $this->get_user( $user_id );

		if ( ! $user ) {
			return $this->get_default_profile();
		}

		$profile = get_user_meta( $user_id, self::META_PROFILE, true );

		if ( ! is_array( $profile ) ) {
			$profile = array();
		}

		$profile = wp_parse_args(
			$profile,
			array(
				'first_name'    => get_user_meta( $user_id, 'first_name', true ),
				'last_name'     => get_user_meta( $user_id, 'last_name', true ),
				'display_name'  => $user->display_name,
				'email'         => $user->user_email,
				'avatar_id'     => absint( get_user_meta( $user_id, self::META_AVATAR, true ) ),
				'bio'           => get_user_meta( $user_id, self::META_BIO, true ),
				'website'       => get_user_meta( $user_id, self::META_WEBSITE, true ),
				'instagram'     => get_user_meta( $user_id, self::META_SOCIAL_INSTAGRAM, true ),
				'tiktok'        => get_user_meta( $user_id, self::META_SOCIAL_TIKTOK, true ),
				'youtube'       => get_user_meta( $user_id, self::META_SOCIAL_YOUTUBE, true ),
				'content_types' => array(),
			)
		);

		$profile['bio'] = (string) get_user_meta( $user_id, self::META_BIO, true ) ?: (string) ( $profile['bio'] ?? '' );
		$profile['avatar_id'] = absint( get_user_meta( $user_id, self::META_AVATAR, true ) ?: ( $profile['avatar_id'] ?? 0 ) );
		$profile['website'] = (string) get_user_meta( $user_id, self::META_WEBSITE, true ) ?: (string) ( $profile['website'] ?? '' );
		$profile['instagram'] = (string) get_user_meta( $user_id, self::META_SOCIAL_INSTAGRAM, true ) ?: (string) ( $profile['instagram'] ?? '' );
		$profile['tiktok'] = (string) get_user_meta( $user_id, self::META_SOCIAL_TIKTOK, true ) ?: (string) ( $profile['tiktok'] ?? '' );
		$profile['youtube'] = (string) get_user_meta( $user_id, self::META_SOCIAL_YOUTUBE, true ) ?: (string) ( $profile['youtube'] ?? '' );
		$profile['content_types'] = $this->sanitize_string_array( get_user_meta( $user_id, self::META_CONTENT_TYPES, true ) ?: ( $profile['content_types'] ?? array() ) );
		$profile['avatar_url'] = $profile['avatar_id'] ? wp_get_attachment_image_url( $profile['avatar_id'], 'thumbnail' ) : '';

		return $profile;
	}

	/**
	 * Update creator profile data.
	 *
	 * @param int                 $user_id User ID.
	 * @param array<string,mixed> $profile Profile fields.
	 * @return bool
	 */
	public function update_creator_profile( int $user_id, array $profile ) {
		$user = $this->get_user( $user_id );

		if ( ! $user ) {
			return false;
		}

		$current_profile = $this->get_creator_profile( $user_id );
		$next_profile    = array_merge( $current_profile, $this->sanitize_profile( $profile ) );

		if ( isset( $profile['first_name'] ) ) {
			update_user_meta( $user_id, 'first_name', sanitize_text_field( $profile['first_name'] ) );
		}

		if ( isset( $profile['last_name'] ) ) {
			update_user_meta( $user_id, 'last_name', sanitize_text_field( $profile['last_name'] ) );
		}

		if ( isset( $profile['display_name'] ) && '' !== trim( (string) $profile['display_name'] ) ) {
			wp_update_user(
				array(
					'ID'           => $user_id,
					'display_name' => sanitize_text_field( $profile['display_name'] ),
				)
			);
		}

		update_user_meta( $user_id, self::META_PROFILE, $next_profile );
		update_user_meta( $user_id, self::META_BIO, $next_profile['bio'] );
		update_user_meta( $user_id, self::META_AVATAR, absint( $next_profile['avatar_id'] ) );
		update_user_meta( $user_id, self::META_WEBSITE, $next_profile['website'] );
		update_user_meta( $user_id, self::META_SOCIAL_INSTAGRAM, $next_profile['instagram'] );
		update_user_meta( $user_id, self::META_SOCIAL_TIKTOK, $next_profile['tiktok'] );
		update_user_meta( $user_id, self::META_SOCIAL_YOUTUBE, $next_profile['youtube'] );
		update_user_meta( $user_id, self::META_CONTENT_TYPES, $next_profile['content_types'] );

		return true;
	}

	/**
	 * Link a user to an invitation record.
	 *
	 * @param int $user_id       User ID.
	 * @param int $invitation_id Invitation ID.
	 * @return bool
	 */
	public function set_invitation_id( int $user_id, int $invitation_id ) {
		$invitation_id = absint( $invitation_id );

		update_user_meta( $user_id, self::META_INVITATION_ID, $invitation_id );
		update_user_meta( $user_id, self::META_INVITATION_ID_LEGACY, $invitation_id );

		return true;
	}

	/**
	 * Get linked invitation ID.
	 *
	 * @param int $user_id User ID.
	 * @return int
	 */
	public function get_invitation_id( int $user_id ) {
		$invitation_id = absint( get_user_meta( $user_id, self::META_INVITATION_ID, true ) );

		if ( $invitation_id > 0 ) {
			return $invitation_id;
		}

		return absint( get_user_meta( $user_id, self::META_INVITATION_ID_LEGACY, true ) );
	}

	/**
	 * Record creator rights acceptance data.
	 *
	 * @param int    $user_id User ID.
	 * @param int    $version Rights version.
	 * @param string $ip      Optional IP.
	 * @return bool
	 */
	public function record_rights_acceptance( int $user_id, int $version, string $ip = '' ) {
		$accepted_at = current_time( 'timestamp' );

		update_user_meta( $user_id, self::META_RIGHTS_AGREED, 1 );
		update_user_meta( $user_id, self::META_RIGHTS_VERSION, max( 1, absint( $version ) ) );
		update_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_AT, $accepted_at );
		update_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_AT_LEGACY, current_time( 'mysql' ) );

		if ( '' !== $ip ) {
			update_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_IP, sanitize_text_field( $ip ) );
			update_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_IP_LEGACY, sanitize_text_field( $ip ) );
		}

		return true;
	}

	/**
	 * Get rights acceptance details.
	 *
	 * @param int $user_id User ID.
	 * @return array<string,mixed>
	 */
	public function get_rights_acceptance( int $user_id ) {
		$accepted_at = get_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_AT, true );

		if ( '' === $accepted_at || null === $accepted_at ) {
			$accepted_at = get_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_AT_LEGACY, true );
		}

		$ip = get_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_IP, true );

		if ( '' === $ip ) {
			$ip = get_user_meta( $user_id, self::META_RIGHTS_ACCEPTED_IP_LEGACY, true );
		}

		return array(
			'agreed'      => ! empty( get_user_meta( $user_id, self::META_RIGHTS_AGREED, true ) ),
			'version'     => absint( get_user_meta( $user_id, self::META_RIGHTS_VERSION, true ) ),
			'accepted_at' => is_numeric( $accepted_at ) ? (int) $accepted_at : $accepted_at,
			'ip'          => $ip,
		);
	}

	/**
	 * Persist creator joined-at timestamp if one is not stored yet.
	 *
	 * @param int $user_id User ID.
	 * @return bool
	 */
	public function mark_creator_joined( int $user_id ) {
		if ( $this->get_creator_joined_at( $user_id ) > 0 ) {
			return true;
		}

		return false !== update_user_meta( $user_id, self::META_JOINED_AT, current_time( 'timestamp' ) );
	}

	/**
	 * Get the creator joined-at timestamp.
	 *
	 * @param int $user_id User ID.
	 * @return int
	 */
	public function get_creator_joined_at( int $user_id ) {
		$joined_at = absint( get_user_meta( $user_id, self::META_JOINED_AT, true ) );

		if ( $joined_at > 0 ) {
			return $joined_at;
		}

		$user = $this->get_user( $user_id );

		return $user ? strtotime( (string) $user->user_registered ) : 0;
	}

	/**
	 * Store creator portal login timestamp.
	 *
	 * @param int $user_id User ID.
	 * @return bool
	 */
	public function touch_last_portal_login( int $user_id ) {
		return false !== update_user_meta( $user_id, self::META_LAST_PORTAL_LOGIN, current_time( 'mysql' ) );
	}

	/**
	 * Get a normalized creator record.
	 *
	 * @param int $user_id User ID.
	 * @return array<string,mixed>|null
	 */
	public function get_creator( int $user_id ): ?array {
		$user = $this->get_user( $user_id );

		if ( ! $user || ! $this->is_creator( $user_id ) ) {
			return null;
		}

		return $this->format_creator_record( $user );
	}

	/**
	 * Query creator users.
	 *
	 * @param array<string,mixed> $filters Query filters.
	 * @return array<int,array<string,mixed>>
	 */
	public function query_creators( array $filters = array() ): array {
		$query = new \WP_User_Query( $this->build_creator_query_args( $filters ) );
		$users = $query->get_results();

		if ( ! is_array( $users ) ) {
			return array();
		}

		$creators = array();
		foreach ( $users as $user ) {
			if ( $user instanceof \WP_User ) {
				$creators[] = $this->format_creator_record( $user );
			}
		}

		return $creators;
	}

	/**
	 * Count creator users.
	 *
	 * @param array<string,mixed> $filters Query filters.
	 * @return int
	 */
	public function count_creators( array $filters = array() ): int {
		$args = $this->build_creator_query_args( $filters );
		$args['number'] = 1;
		$args['offset'] = 0;
		$args['fields'] = 'ID';
		$args['count_total'] = true;

		$query = new \WP_User_Query( $args );

		return (int) $query->get_total();
	}

	/**
	 * Get the default profile shape.
	 *
	 * @return array<string,mixed>
	 */
	private function get_default_profile() {
		return array(
			'first_name'    => '',
			'last_name'     => '',
			'display_name'  => '',
			'email'         => '',
			'avatar_id'     => 0,
			'bio'           => '',
			'website'       => '',
			'instagram'     => '',
			'tiktok'        => '',
			'youtube'       => '',
			'content_types' => array(),
		);
	}

	/**
	 * Sanitize creator profile input.
	 *
	 * @param array<string,mixed> $profile Raw profile.
	 * @return array<string,mixed>
	 */
	private function sanitize_profile( array $profile ) {
		$sanitized = array();

		if ( array_key_exists( 'first_name', $profile ) ) {
			$sanitized['first_name'] = sanitize_text_field( $profile['first_name'] );
		}

		if ( array_key_exists( 'last_name', $profile ) ) {
			$sanitized['last_name'] = sanitize_text_field( $profile['last_name'] );
		}

		if ( array_key_exists( 'display_name', $profile ) ) {
			$sanitized['display_name'] = sanitize_text_field( $profile['display_name'] );
		}

		if ( array_key_exists( 'avatar_id', $profile ) ) {
			$sanitized['avatar_id'] = absint( $profile['avatar_id'] );
		}

		if ( array_key_exists( 'bio', $profile ) ) {
			$sanitized['bio'] = sanitize_textarea_field( $profile['bio'] );
		}

		if ( array_key_exists( 'website', $profile ) ) {
			$sanitized['website'] = esc_url_raw( $profile['website'] );
		}

		foreach ( array( 'instagram', 'tiktok', 'youtube' ) as $social_key ) {
			if ( array_key_exists( $social_key, $profile ) ) {
				$sanitized[ $social_key ] = sanitize_text_field( $profile[ $social_key ] );
			}
		}

		if ( array_key_exists( 'content_types', $profile ) ) {
			$sanitized['content_types'] = $this->sanitize_string_array( $profile['content_types'] );
		}

		return $sanitized;
	}

	/**
	 * Sanitize creator status values.
	 *
	 * @param string $status Raw status.
	 * @return string
	 */
	private function sanitize_status( string $status ) {
		$allowed = array( 'active', 'inactive', 'suspended', 'deactivated' );
		$status  = sanitize_key( $status );

		if ( ! in_array( $status, $allowed, true ) ) {
			return 'inactive';
		}

		return $status;
	}

	/**
	 * Sanitize arrays of string values.
	 *
	 * @param mixed $values Raw values.
	 * @return array<int,string>
	 */
	private function sanitize_string_array( $values ) {
		if ( ! is_array( $values ) ) {
			return array();
		}

		return array_values(
			array_unique(
				array_filter(
					array_map( 'sanitize_text_field', $values )
				)
			)
		);
	}

	/**
	 * Build creator query arguments.
	 *
	 * @param array<string,mixed> $filters Query filters.
	 * @return array<string,mixed>
	 */
	private function build_creator_query_args( array $filters ): array {
		$args = array(
			'role'        => class_exists( '\\Ugcify\\Influencer\\Capabilities' ) ? \Ugcify\Influencer\Capabilities::ROLE : 'ugcify_influencer',
			'number'      => max( 1, absint( $filters['limit'] ?? 20 ) ),
			'offset'      => max( 0, absint( $filters['offset'] ?? 0 ) ),
			'orderby'     => 'registered',
			'order'       => 'ASC' === strtoupper( (string) ( $filters['order'] ?? 'DESC' ) ) ? 'ASC' : 'DESC',
			'fields'      => 'all_with_meta',
			'count_total' => false,
		);

		if ( ! empty( $filters['search'] ) ) {
			$search = trim( sanitize_text_field( $filters['search'] ) );

			if ( '' !== $search ) {
				$args['search'] = '*' . $search . '*';
				$args['search_columns'] = array( 'user_email', 'display_name', 'user_login' );
			}
		}

		$meta_query = array();
		$statuses = $this->normalize_status_filter( $filters['status'] ?? array() );

		if ( ! empty( $statuses ) ) {
			$meta_query[] = array(
				'key'     => self::META_STATUS,
				'value'   => 1 === count( $statuses ) ? $statuses[0] : $statuses,
				'compare' => 1 === count( $statuses ) ? '=' : 'IN',
			);
		}

		if ( ! empty( $filters['invitation_id'] ) ) {
			$meta_query[] = array(
				'key'     => self::META_INVITATION_ID,
				'value'   => absint( $filters['invitation_id'] ),
				'compare' => '=',
			);
		}

		if ( ! empty( $meta_query ) ) {
			$args['meta_query'] = $meta_query;
		}

		return $args;
	}

	/**
	 * Format a creator record.
	 *
	 * @param \WP_User $user User object.
	 * @return array<string,mixed>
	 */
	private function format_creator_record( \WP_User $user ): array {
		$user_id = (int) $user->ID;

		return array(
			'user_id'           => $user_id,
			'email'             => $user->user_email,
			'display_name'      => $user->display_name,
			'registered_at'     => $user->user_registered,
			'joined_at'         => $this->get_creator_joined_at( $user_id ),
			'status'            => $this->get_creator_status( $user_id ),
			'invitation_id'     => $this->get_invitation_id( $user_id ),
			'last_portal_login' => get_user_meta( $user_id, self::META_LAST_PORTAL_LOGIN, true ),
			'profile'           => $this->get_creator_profile( $user_id ),
			'rights'            => $this->get_rights_acceptance( $user_id ),
		);
	}

	/**
	 * Normalize creator status filters.
	 *
	 * @param mixed $status Raw status filter.
	 * @return array<int,string>
	 */
	private function normalize_status_filter( $status ): array {
		$allowed_statuses = array( 'active', 'inactive', 'suspended', 'deactivated' );

		if ( is_string( $status ) && '' !== $status ) {
			$status = array( $status );
		}

		if ( ! is_array( $status ) ) {
			return array();
		}

		$status = array_map( 'sanitize_key', $status );
		$status = array_values( array_intersect( $allowed_statuses, $status ) );

		return array_values(
			array_unique(
				array_filter(
					$status
				)
			)
		);
	}
}
