<?php

declare(strict_types=1);

namespace Kreait\Firebase\Auth;

use Beste\Json;
use DateTimeImmutable;
use Kreait\Firebase\Util\DT;

use function array_key_exists;
use function array_map;

/**
 * @phpstan-import-type ProviderUserInfoResponseShape from UserInfo
 * @phpstan-import-type UserMetadataResponseShape from UserMetaData
 * @phpstan-import-type MfaInfoResponseShape from MfaInfo
 *
 * @phpstan-type UserRecordResponseShape array{
 *     localId: non-empty-string,
 *     email?: non-empty-string,
 *     emailVerified?: bool,
 *     displayName?: non-empty-string,
 *     photoUrl?: non-empty-string,
 *     phoneNumber?: non-empty-string,
 *     disabled?: bool,
 *     passwordHash?: non-empty-string,
 *     salt?: non-empty-string,
 *     customAttributes?: non-empty-string,
 *     tenantId?: non-empty-string,
 *     providerUserInfo?: list<ProviderUserInfoResponseShape>,
 *     mfaInfo?: list<MfaInfoResponseShape>,
 *     createdAt: non-empty-string,
 *     lastLoginAt?: non-empty-string,
 *     passwordUpdatedAt?: non-empty-string,
 *     lastRefreshAt?: non-empty-string,
 *     validSince?: non-empty-string
 * }
 */
final class UserRecord
{
    /**
     * @param non-empty-string $uid
     * @param non-empty-string|null $email
     * @param non-empty-string|null $displayName
     * @param non-empty-string|null $phoneNumber
     * @param non-empty-string|null $photoUrl
     * @param list<UserInfo> $providerData
     * @param non-empty-string|null $passwordHash
     * @param non-empty-string|null $passwordSalt
     * @param array<non-empty-string, mixed> $customClaims
     * @param non-empty-string|null $tenantId
     */
    public function __construct(
        public readonly string $uid,
        public readonly ?string $email,
        public readonly bool $emailVerified,
        public readonly ?string $displayName,
        public readonly ?string $phoneNumber,
        public readonly ?string $photoUrl,
        public readonly bool $disabled,
        public readonly UserMetaData $metadata,
        public readonly array $providerData,
        public readonly ?MfaInfo $mfaInfo,
        public readonly ?string $passwordHash,
        public readonly ?string $passwordSalt,
        public readonly array $customClaims,
        public readonly ?string $tenantId,
        public readonly ?DateTimeImmutable $tokensValidAfterTime,
    ) {
    }

    /**
     * @internal
     *
     * @param UserRecordResponseShape $data
     */
    public static function fromResponseData(array $data): self
    {
        $validSince = array_key_exists('validSince', $data)
            ? DT::toUTCDateTimeImmutable($data['validSince'])
            : null;

        $customClaims = array_key_exists('customAttributes', $data)
            ? Json::decode($data['customAttributes'], true)
            : [];

        $providerUserInfo = array_key_exists('providerUserInfo', $data)
            ? self::userInfoFromResponseData($data)
            : [];

        return new self(
            $data['localId'],
            $data['email'] ?? null,
            $data['emailVerified'] ?? false,
            $data['displayName'] ?? null,
            $data['phoneNumber'] ?? null,
            $data['photoUrl'] ?? null,
            $data['disabled'] ?? false,
            self::userMetaDataFromResponseData($data),
            $providerUserInfo,
            self::mfaInfoFromResponseData($data),
            $data['passwordHash'] ?? null,
            $data['salt'] ?? null,
            $customClaims,
            $data['tenantId'] ?? null,
            $validSince,
        );
    }

    /**
     * @param UserMetadataResponseShape $data
     */
    private static function userMetaDataFromResponseData(array $data): UserMetaData
    {
        return UserMetaData::fromResponseData($data);
    }

    /**
     * @param array{
     *     mfaInfo?: list<MfaInfoResponseShape>
     * } $data
     */
    private static function mfaInfoFromResponseData(array $data): ?MfaInfo
    {
        if (!array_key_exists('mfaInfo', $data)) {
            return null;
        }

        $mfaInfo = array_shift($data['mfaInfo']);

        if ($mfaInfo === null) {
            return null;
        }

        return MfaInfo::fromResponseData($mfaInfo);
    }

    /**
     * @param array{providerUserInfo: list<ProviderUserInfoResponseShape>} $data
     *
     * @return list<UserInfo>
     */
    private static function userInfoFromResponseData(array $data): array
    {
        return array_map(
            static fn(array $userInfoData) => UserInfo::fromResponseData($userInfoData),
            $data['providerUserInfo'],
        );
    }
}
