<?php

namespace RSSSL\Security\WordPress\Two_Fa\Models;

use RSSSL\Security\WordPress\Two_Fa\Services\Rsssl_Two_Fa_Status_Service;
use WP_User;

class Rsssl_Two_Factor_User_Factory {

	/**
	 * Defines a role hierarchy.
	 *
	 * @var array<string, int>
	 */
	protected array $roleHierarchy = [
		'administrator' => 100,
		'editor'        => 80,
		'author'        => 60,
		'subscriber'    => 40,
		'contributor'   => 20,
	];

	private Rsssl_Two_Fa_Status_Service $statusService;

	/**
	 * Inject the status service.
	 *
	 */
	public function __construct() {
		$this->statusService = new Rsssl_Two_Fa_Status_Service();
	}

	/**
	 * Create a TwoFaUser from a WP_User object.
	 *
	 * @return Rsssl_Two_FA_user|null Returns null if the user has no roles.
	 */
	public function createFromWPUser( WP_User $user, array $forcedRoles, array $enabledRoles, int $daysThreshold ): ?Rsssl_Two_FA_user {
		// Retrieve user roles.
		$userRoles = $user->roles;
		if ( is_multisite() && empty( $userRoles ) ) {
			$userRoles = [];

			// On multisite, roles are stored per site under `<blog_prefix>capabilities` user meta.
			// get_blogs_of_user() does not reliably include the role; fetch capabilities per site instead.
			$blogs = get_blogs_of_user( $user->ID );
			if ( ! empty( $blogs ) && is_array( $blogs ) ) {
				global $wpdb;
				foreach ( $blogs as $blog ) {
					// Determine a blog ID property that exists on this object/array.
					$blogId = 0;
					if ( is_object( $blog ) ) {
						$blogId = isset( $blog->userblog_id ) ? (int) $blog->userblog_id : ( isset( $blog->blog_id ) ? (int) $blog->blog_id : ( isset( $blog->id ) ? (int) $blog->id : 0 ) );
					} elseif ( is_array( $blog ) ) {
						$blogId = isset( $blog['userblog_id'] ) ? (int) $blog['userblog_id'] : ( isset( $blog['blog_id'] ) ? (int) $blog['blog_id'] : ( isset( $blog['id'] ) ? (int) $blog['id'] : 0 ) );
					}

					if ( $blogId > 0 ) {
						$prefix    = $wpdb->get_blog_prefix( $blogId );
						$caps      = get_user_meta( $user->ID, $prefix . 'capabilities', true );
						if ( is_array( $caps ) ) {
							// Collect roles where the capability is truthy.
							$rolesForSite = array_keys( array_filter( $caps ) );
							if ( ! empty( $rolesForSite ) ) {
								$userRoles = array_merge( $userRoles, $rolesForSite );
							}
						}
					}
				}
			}

			// Fall back for network admins who may not have per-site roles.
			if ( function_exists( 'is_super_admin' ) && is_super_admin( $user->ID ) ) {
				$userRoles[] = 'administrator';
			}

			$userRoles = array_values( array_unique( $userRoles ) );
		}

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

		// Use the status service to determine the user's status.
		$statusForUser = $this->statusService->determineStatus( $user->ID, $forcedRoles, $daysThreshold );

		// Determine two-factor provider.
		$provider = $this->determineTwoFaProvider( $user->ID );

		// Identify matching roles.
		$matchingRoles = array_intersect( $userRoles, $enabledRoles );


		// If multiple roles exist and one of them is forced, prefer the forced role.
		if ( ! empty( $forcedRoles ) && count( $matchingRoles ) > 1 ) {
			$matchingForcedRoles = array_intersect( $matchingRoles, $forcedRoles );
			if ( ! empty( $matchingForcedRoles ) ) {
				$matchingRoles = $matchingForcedRoles;
			}
		}

		// If multiple roles remain, choose the most important one based on the defined hierarchy.
		if ( count( $matchingRoles ) > 1 ) {
			usort( $matchingRoles, function ( $role1, $role2 ) {
				$priority1 = $this->roleHierarchy[ $role1 ] ?? 0;
				$priority2 = $this->roleHierarchy[ $role2 ] ?? 0;

				return $priority2 <=> $priority1;
			} );
		}

		return new Rsssl_Two_FA_user (
			$user->ID,
			$user->user_login,
			$statusForUser,
			$provider,
			$userRoles
		);
	}

	/**
	 * Determine the active two-factor provider.
	 */
	protected function determineTwoFaProvider( int $userId ): string {
		$providers = [
			[ 'provider' => 'totp', 'meta_key' => 'rsssl_two_fa_status_totp' ],
			[ 'provider' => 'email', 'meta_key' => 'rsssl_two_fa_status_email' ],
			[ 'provider' => 'passkey', 'meta_key' => 'rsssl_two_fa_status_passkey' ],
		];

		foreach ( $providers as $entry ) {
			if ( get_user_meta( $userId, $entry['meta_key'], true ) === 'active' ) {
				return $entry['provider'];
			}
		}

		return 'none';
	}
}