// <copyright file="Credential.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>

using OpenQA.Selenium.Internal;
using System;
using System.Collections.Generic;

#nullable enable

namespace OpenQA.Selenium.VirtualAuth
{
    /// <summary>
    /// A credential stored in a virtual authenticator.
    /// Refer <see href="https://w3c.github.io/webauthn/#credential-parameters"/>
    /// </summary>
    public sealed class Credential
    {
        private readonly byte[] id;
        private readonly byte[]? userHandle;

        private Credential(byte[] id, bool isResidentCredential, string rpId, string privateKey, byte[]? userHandle, int signCount)
        {
            this.id = id ?? throw new ArgumentNullException(nameof(id));
            this.IsResidentCredential = isResidentCredential;
            this.RpId = rpId ?? throw new ArgumentNullException(nameof(rpId));
            this.PrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
            this.userHandle = userHandle;
            this.SignCount = signCount;
        }

        /// <summary>
        /// Creates a credential for use with a virtual authenticator.
        /// </summary>
        /// <param name="id">A byte array representing the ID of the credentials.</param>
        /// <param name="rpId">The ID of the relying party to which the credential is scoped.</param>
        /// <param name="privateKey">The private Key for the credentials.</param>
        /// <param name="signCount">The signature counter for the credentials.</param>
        /// <returns>The created instance of the Credential class.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="id"/>, <paramref name="rpId"/>, or <paramref name="privateKey"/> are <see langword="null"/>.</exception>
        public static Credential CreateNonResidentCredential(byte[] id, string rpId, string privateKey, int signCount)
        {
            return new Credential(id, false, rpId, privateKey, null, signCount);
        }

        /// <summary>
        /// Creates a credential for use with a virtual authenticator.
        /// </summary>
        /// <param name="id">A byte array representing the ID of the credentials.</param>
        /// <param name="rpId">The ID of the relying party to which the credential is scoped.</param>
        /// <param name="privateKey">The private Key for the credentials.</param>
        /// <param name="userHandle">The user handle associated to the credential.</param>
        /// <param name="signCount">The signature counter for the credentials.</param>
        /// <returns>The created instance of the Credential class.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="id"/>, <paramref name="rpId"/>, or <paramref name="privateKey"/> are <see langword="null"/>.</exception>
        public static Credential CreateResidentCredential(byte[] id, string rpId, string privateKey, byte[] userHandle, int signCount)
        {
            return new Credential(id, true, rpId, privateKey, userHandle, signCount);
        }

        /// <summary>
        /// Gets the byte array of the ID of the credential.
        /// </summary>
        public byte[] Id => (byte[])id.Clone();

        /// <summary>
        /// Gets a value indicating whether this Credential is a resident credential.
        /// </summary>
        public bool IsResidentCredential { get; }

        /// <summary>
        /// Gets the ID of the relying party of this credential.
        /// </summary>
        public string RpId { get; }

        /// <summary>
        /// Gets the private key of the credential.
        /// </summary>
        public string PrivateKey { get; }

        /// <summary>
        /// Gets the user handle of the credential.
        /// </summary>
        public byte[]? UserHandle => (byte[]?)userHandle?.Clone();

        /// <summary>
        /// Gets the signature counter associated to the public key credential source.
        /// </summary>
        public int SignCount { get; }

        /// <summary>
        /// Creates a Credential instance from a dictionary of values.
        /// </summary>
        /// <param name="dictionary">The dictionary of values to use to create the Credential instance.</param>
        /// <returns>The created instance of the Credential.</returns>
        public static Credential FromDictionary(Dictionary<string, object> dictionary)
        {
            byte[] id = Base64UrlEncoder.DecodeBytes((string)dictionary["credentialId"]);
            bool isResidentCredential = (bool)dictionary["isResidentCredential"];
            string? rpId = dictionary.TryGetValue("rpId", out object? r) ? (string)r : null;
            string privateKey = (string)dictionary["privateKey"];
            byte[]? userHandle = dictionary.TryGetValue("userHandle", out object? u) ? Base64UrlEncoder.DecodeBytes((string)u) : null;
            int signCount = (int)(long)dictionary["signCount"];

            return new Credential(id, isResidentCredential, rpId, privateKey, userHandle, signCount);
        }

        /// <summary>
        /// Serializes this Credential instance to a dictionary.
        /// </summary>
        /// <returns>The dictionary containing the values for this Credential.</returns>
        public Dictionary<string, object> ToDictionary()
        {
            Dictionary<string, object> toReturn = new Dictionary<string, object>();

            toReturn["credentialId"] = Base64UrlEncoder.Encode(this.id);
            toReturn["isResidentCredential"] = this.IsResidentCredential;
            toReturn["rpId"] = this.RpId;
            toReturn["privateKey"] = this.PrivateKey;
            toReturn["signCount"] = this.SignCount;
            if (this.userHandle is not null)
            {
                toReturn["userHandle"] = Base64UrlEncoder.Encode(this.userHandle);
            }

            return toReturn;
        }
    }
}
