//
//  UniWebViewAuthenticationCommonFlow.cs
//  Created by Wang Wei (@onevcat) on 2022-06-25.
//
//  This file is a part of UniWebView Project (https://uniwebview.com)
//  By purchasing the asset, you are allowed to use this code in as many as projects 
//  you want, only if you publish the final products under the name of the same account
//  used for the purchase. 
//
//  This asset and all corresponding files (such as source code) are provided on an 
//  “as is” basis, without warranty of any kind, express of implied, including but not
//  limited to the warranties of merchantability, fitness for a particular purpose, and 
//  noninfringement. In no event shall the authors or copyright holders be liable for any 
//  claim, damages or other liability, whether in action of contract, tort or otherwise, 
//  arising from, out of or in connection with the software or the use of other dealing in the software.
//

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Abstract class and general control for other authentication flows. This class determines the global behaviors of the 
/// authentication flow, such as whether to start authentication as soon as the script `Start`s, and whether to use private
/// mode to authenticate the user.

/// This is a super and abstract class for all concrete auth flow. You are not expected to use this class directly.
/// Instead, to start a customized auth flow, you can use the `UniWebViewAuthenticationFlowCustomize` class.
/// </summary>
public abstract class UniWebViewAuthenticationCommonFlow: MonoBehaviour {
    /// <summary>
    /// Whether to start authentication as soon as the script `Start`s.
    /// </summary>
    public bool authorizeOnStart;
    /// <summary>
    /// Whether to use private mode to authenticate the user. If `true` and the device supports, the authentication
    /// will begin under the incognito mode.
    ///
    /// On iOS, this works on iOS 13 and later.
    ///
    /// On Android, it depends on the Chrome version and might require users to enable the incognito mode (and support
    /// for third-party use) in Chrome's settings. Check settings with `chrome://flags/#cct-incognito` and
    /// `chrome://flags/#cct-incognito-available-to-third-party` in Chrome to see the current status.
    /// </summary>
    public bool privateMode;
    
    // Security. Store the state.
    private string state;
    // Security. Store the code challenge verifier. 
    private string codeVerify;
    
    protected string CodeVerify => codeVerify;
    
    public void Start() {
        if (authorizeOnStart) {
            StartAuthenticationFlow();
        }
    }

    // Subclass should override this method to start the authentication flow. Usually it starts
    // a `UniWebViewAuthenticationFlow`. But you can also choose whatever you need to do.
    public abstract void StartAuthenticationFlow();

    // Child classes are expected to call this method to request a `state` (and store it for later check) if the 
    // `state` verification is enabled.
    protected string GenerateAndStoreState() {
        state = UniWebViewAuthenticationUtils.GenerateRandomBase64URLString();
        return state;
    }

    // Child classes are expected to call this method to request a `code_challenge`. Later when exchanging the access
    // token, the `code_verifier` will be used to verify the `code_challenge`. Subclass can read it from `CodeVerify`.
    protected string GenerateCodeChallengeAndStoreCodeVerify(UniWebViewAuthenticationPKCE method) {
        codeVerify = UniWebViewAuthenticationUtils.GenerateCodeVerifier();
        return UniWebViewAuthenticationUtils.CalculateCodeChallenge(codeVerify, method);
    }
    
    // Perform verifying for `state`.
    protected void VerifyState(Dictionary<string, string> parameters, string key = "state") {
        if (state == null) {
            throw AuthenticationResponseException.InvalidState;
        }
        if (!parameters.TryGetValue(key, out var stateInResponse) || state != stateInResponse) {
            throw AuthenticationResponseException.InvalidState;
        }
    }
}
