// Copyright (c) The LEGO Group. All rights reserved.

namespace LEGO.AsyncAPI.Models
{
    using System;
    using System.Collections.Generic;
    using LEGO.AsyncAPI.Models.Interfaces;
    using LEGO.AsyncAPI.Writers;

    public class AsyncApiSecurityScheme : IAsyncApiSerializable, IAsyncApiReferenceable, IAsyncApiExtensible
    {
        /// <summary>
        /// REQUIRED. The type of the security scheme. Valid values are "userPassword", "apiKey", "X509", "symmetricEncryption", "asymmetricEncryption", "httpApiKey", "http", "oauth2", "openIdConnect", "plain", "scramSha256", "scramSha512", and "gssapi".
        /// </summary>
        public SecuritySchemeType Type { get; set; }

        /// <summary>
        /// A short description for security scheme. CommonMark syntax MAY be used for rich text representation.
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// REQUIRED. The name of the header, query or cookie parameter to be used.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// REQUIRED. The location of the API key. Valid values are "user" and "password" for apiKey and "query", "header" or "cookie" for httpApiKey.
        /// </summary>
        public ParameterLocation In { get; set; }

        /// <summary>
        /// REQUIRED. The name of the HTTP Authorization scheme to be used
        /// in the Authorization header as defined in RFC7235.
        /// </summary>
        public string Scheme { get; set; }

        /// <summary>
        /// A hint to the client to identify how the bearer token is formatted.
        /// Bearer tokens are usually generated by an authorization server,
        /// so this information is primarily for documentation purposes.
        /// </summary>
        public string BearerFormat { get; set; }

        /// <summary>
        /// REQUIRED. An object containing configuration information for the flow types supported.
        /// </summary>
        public AsyncApiOAuthFlows Flows { get; set; }

        /// <summary>
        /// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values.
        /// </summary>
        public Uri OpenIdConnectUrl { get; set; }

        /// <summary>
        /// Specification Extensions.
        /// </summary>
        public IDictionary<string, IAsyncApiExtension> Extensions { get; set; } = new Dictionary<string, IAsyncApiExtension>();

        /// <summary>
        /// Indicates if object is populated with data or is just a reference to the data.
        /// </summary>
        public bool UnresolvedReference { get; set; }

        /// <summary>
        /// Reference object.
        /// </summary>
        public AsyncApiReference Reference { get; set; }

        /// <summary>
        /// Serialize <see cref="AsyncApiSecurityScheme"/> to Async Api v2.3.
        /// </summary>
        public void SerializeV2(IAsyncApiWriter writer)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (this.Reference != null)
            {
                this.Reference.SerializeV2(writer);
                return;
            }

            this.SerializeV2WithoutReference(writer);
        }

        /// <summary>
        /// Serialize to AsyncApi V2 document without using reference.
        /// </summary>
        public void SerializeV2WithoutReference(IAsyncApiWriter writer)
        {
            writer.WriteStartObject();

            // type
            writer.WriteRequiredProperty(AsyncApiConstants.Type, this.Type.GetDisplayName());

            // description
            writer.WriteOptionalProperty(AsyncApiConstants.Description, this.Description);

            switch (this.Type)
            {
                case SecuritySchemeType.UserPassword:
                    break;
                case SecuritySchemeType.ApiKey:
                    writer.WriteOptionalProperty(AsyncApiConstants.In, this.In.GetDisplayName());
                    break;
                case SecuritySchemeType.X509:
                    break;
                case SecuritySchemeType.SymmetricEncryption:
                    break;
                case SecuritySchemeType.AsymmetricEncryption:
                    break;
                case SecuritySchemeType.HttpApiKey:
                    writer.WriteOptionalProperty(AsyncApiConstants.Name, this.Name);
                    writer.WriteOptionalProperty(AsyncApiConstants.In, this.In.GetDisplayName());
                    break;
                case SecuritySchemeType.Http:
                    writer.WriteOptionalProperty(AsyncApiConstants.Scheme, this.Scheme);
                    writer.WriteOptionalProperty(AsyncApiConstants.BearerFormat, this.BearerFormat);
                    break;
                case SecuritySchemeType.OAuth2:
                    writer.WriteOptionalObject(AsyncApiConstants.Flows, this.Flows, (w, o) => o.SerializeV2(w));
                    break;
                case SecuritySchemeType.OpenIdConnect:
                    writer.WriteOptionalProperty(AsyncApiConstants.OpenIdConnectUrl, this.OpenIdConnectUrl?.ToString());
                    break;
                case SecuritySchemeType.Plain:
                    break;
                case SecuritySchemeType.ScramSha256:
                    break;
                case SecuritySchemeType.ScramSha512:
                    break;
                case SecuritySchemeType.Gssapi:
                    break;
                default:
                    break;
            }

            // extensions
            writer.WriteExtensions(this.Extensions);

            writer.WriteEndObject();
        }

        /// <summary>
        /// Arbitrarily chooses one <see cref="AsyncApiOAuthFlow"/> object from the <see cref="AsyncApiOAuthFlows"/>
        /// to populate in V2 security scheme.
        /// </summary>
        private static void WriteOAuthFlowForV2(IAsyncApiWriter writer, AsyncApiOAuthFlows flows)
        {
            if (flows != null)
            {
                if (flows.Implicit != null)
                {
                    WriteOAuthFlowForV2(writer, AsyncApiConstants.Implicit, flows.Implicit);
                }
                else if (flows.Password != null)
                {
                    WriteOAuthFlowForV2(writer, AsyncApiConstants.Password, flows.Password);
                }
                else if (flows.ClientCredentials != null)
                {
                    WriteOAuthFlowForV2(writer, AsyncApiConstants.Application, flows.ClientCredentials);
                }
                else if (flows.AuthorizationCode != null)
                {
                    WriteOAuthFlowForV2(writer, AsyncApiConstants.AccessCode, flows.AuthorizationCode);
                }
            }
        }

        private static void WriteOAuthFlowForV2(IAsyncApiWriter writer, string flowValue, AsyncApiOAuthFlow flow)
        {
            // flow
            writer.WriteOptionalProperty(AsyncApiConstants.Flow, flowValue);

            // authorizationUrl
            writer.WriteOptionalProperty(AsyncApiConstants.AuthorizationUrl, flow.AuthorizationUrl?.ToString());

            // tokenUrl
            writer.WriteOptionalProperty(AsyncApiConstants.TokenUrl, flow.TokenUrl?.ToString());

            // scopes
            writer.WriteOptionalMap(AsyncApiConstants.Scopes, flow.Scopes, (w, s) => w.WriteValue(s));
        }
    }
}