using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Services;
using Bit.Core.Sso;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

namespace Bit.Api.Auth.Models.Request.Organizations;

public class OrganizationSsoRequestModel
{
    [Required]
    public bool Enabled { get; set; }
    [StringLength(50)]
    public string Identifier { get; set; }
    [Required]
    public SsoConfigurationDataRequest Data { get; set; }

    public SsoConfig ToSsoConfig(Guid organizationId)
    {
        return ToSsoConfig(new SsoConfig { OrganizationId = organizationId });
    }

    public SsoConfig ToSsoConfig(SsoConfig existingConfig)
    {
        existingConfig.Enabled = Enabled;
        var configurationData = Data.ToConfigurationData();
        existingConfig.SetData(configurationData);
        return existingConfig;
    }
}

public class SsoConfigurationDataRequest : IValidatableObject
{
    public SsoConfigurationDataRequest() { }

    [Required]
    public SsoType ConfigType { get; set; }
    public MemberDecryptionType MemberDecryptionType { get; set; }

    [Obsolete("Use MemberDecryptionType instead")]
    public bool KeyConnectorEnabled
    {
        // Setter is kept for backwards compatibility with older clients that still use this property.
        set { MemberDecryptionType = value ? MemberDecryptionType.KeyConnector : MemberDecryptionType.MasterPassword; }
    }
    public string KeyConnectorUrl { get; set; }

    // OIDC
    public string Authority { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string MetadataAddress { get; set; }
    public OpenIdConnectRedirectBehavior RedirectBehavior { get; set; }
    public bool? GetClaimsFromUserInfoEndpoint { get; set; }
    public string AdditionalScopes { get; set; }
    public string AdditionalUserIdClaimTypes { get; set; }
    public string AdditionalEmailClaimTypes { get; set; }
    public string AdditionalNameClaimTypes { get; set; }
    public string AcrValues { get; set; }
    public string ExpectedReturnAcrValue { get; set; }

    // SAML2 SP
    public bool? SpUniqueEntityId { get; set; }
    public Saml2NameIdFormat SpNameIdFormat { get; set; }
    public string SpOutboundSigningAlgorithm { get; set; }
    public Saml2SigningBehavior SpSigningBehavior { get; set; }
    public bool? SpWantAssertionsSigned { get; set; }
    public bool? SpValidateCertificates { get; set; }
    public string SpMinIncomingSigningAlgorithm { get; set; }

    // SAML2 IDP
    public string IdpEntityId { get; set; }
    public Saml2BindingType IdpBindingType { get; set; }
    public string IdpSingleSignOnServiceUrl { get; set; }
    public string IdpSingleLogoutServiceUrl { get; set; }
    public string IdpArtifactResolutionServiceUrl { get => null; set { /*IGNORE*/ } }
    public string IdpX509PublicCert { get; set; }
    public string IdpOutboundSigningAlgorithm { get; set; }
    public bool? IdpAllowUnsolicitedAuthnResponse { get; set; }
    public bool? IdpDisableOutboundLogoutRequests { get; set; }
    public bool? IdpWantAuthnRequestsSigned { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext context)
    {
        var i18nService = context.GetService(typeof(II18nService)) as I18nService;

        if (ConfigType == SsoType.OpenIdConnect)
        {
            if (string.IsNullOrWhiteSpace(Authority))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("AuthorityValidationError"),
                    new[] { nameof(Authority) });
            }

            if (string.IsNullOrWhiteSpace(ClientId))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientIdValidationError"),
                    new[] { nameof(ClientId) });
            }

            if (string.IsNullOrWhiteSpace(ClientSecret))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientSecretValidationError"),
                    new[] { nameof(ClientSecret) });
            }
        }
        else if (ConfigType == SsoType.Saml2)
        {
            if (string.IsNullOrWhiteSpace(IdpEntityId))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpEntityIdValidationError"),
                    new[] { nameof(IdpEntityId) });
            }

            if (!Uri.IsWellFormedUriString(IdpEntityId, UriKind.Absolute) && string.IsNullOrWhiteSpace(IdpSingleSignOnServiceUrl))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"),
                    new[] { nameof(IdpSingleSignOnServiceUrl) });
            }

            if (InvalidServiceUrl(IdpSingleSignOnServiceUrl))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlInvalid"),
                    new[] { nameof(IdpSingleSignOnServiceUrl) });
            }

            if (InvalidServiceUrl(IdpSingleLogoutServiceUrl))
            {
                yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleLogoutServiceUrlInvalid"),
                    new[] { nameof(IdpSingleLogoutServiceUrl) });
            }

            if (!string.IsNullOrWhiteSpace(IdpX509PublicCert))
            {
                // Validate the certificate is in a valid format
                ValidationResult failedResult = null;
                try
                {
                    var certData = CoreHelpers.Base64UrlDecode(StripPemCertificateElements(IdpX509PublicCert));
                    new X509Certificate2(certData);
                }
                catch (FormatException)
                {
                    failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertInvalidFormatValidationError"),
                        new[] { nameof(IdpX509PublicCert) });
                }
                catch (CryptographicException cryptoEx)
                {
                    failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertCryptographicExceptionValidationError", cryptoEx.Message),
                        new[] { nameof(IdpX509PublicCert) });
                }
                catch (Exception ex)
                {
                    failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertValidationError", ex.Message),
                        new[] { nameof(IdpX509PublicCert) });
                }
                if (failedResult != null)
                {
                    yield return failedResult;
                }
            }
        }
    }

    public SsoConfigurationData ToConfigurationData()
    {
        return new SsoConfigurationData
        {
            ConfigType = ConfigType,
            MemberDecryptionType = MemberDecryptionType,
            KeyConnectorUrl = KeyConnectorUrl,
            Authority = Authority,
            ClientId = ClientId,
            ClientSecret = ClientSecret,
            MetadataAddress = MetadataAddress,
            GetClaimsFromUserInfoEndpoint = GetClaimsFromUserInfoEndpoint.GetValueOrDefault(),
            RedirectBehavior = RedirectBehavior,
            IdpEntityId = IdpEntityId,
            IdpBindingType = IdpBindingType,
            IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl,
            IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl,
            IdpArtifactResolutionServiceUrl = null,
            IdpX509PublicCert = StripPemCertificateElements(IdpX509PublicCert),
            IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm,
            IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse.GetValueOrDefault(),
            IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests.GetValueOrDefault(),
            IdpWantAuthnRequestsSigned = IdpWantAuthnRequestsSigned.GetValueOrDefault(),
            SpUniqueEntityId = SpUniqueEntityId.GetValueOrDefault(),
            SpNameIdFormat = SpNameIdFormat,
            SpOutboundSigningAlgorithm = SpOutboundSigningAlgorithm ?? SamlSigningAlgorithms.Sha256,
            SpSigningBehavior = SpSigningBehavior,
            SpWantAssertionsSigned = SpWantAssertionsSigned.GetValueOrDefault(),
            SpValidateCertificates = SpValidateCertificates.GetValueOrDefault(),
            SpMinIncomingSigningAlgorithm = SpMinIncomingSigningAlgorithm,
            AdditionalScopes = AdditionalScopes,
            AdditionalUserIdClaimTypes = AdditionalUserIdClaimTypes,
            AdditionalEmailClaimTypes = AdditionalEmailClaimTypes,
            AdditionalNameClaimTypes = AdditionalNameClaimTypes,
            AcrValues = AcrValues,
            ExpectedReturnAcrValue = ExpectedReturnAcrValue,
        };
    }

    private string StripPemCertificateElements(string certificateText)
    {
        if (string.IsNullOrWhiteSpace(certificateText))
        {
            return null;
        }
        return Regex.Replace(certificateText,
            @"(((BEGIN|END) CERTIFICATE)|([\-\n\r\t\s\f]))",
            string.Empty,
            RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
    }

    private bool InvalidServiceUrl(string url)
    {
        if (string.IsNullOrWhiteSpace(url))
        {
            return false;
        }
        if (!url.StartsWith("http://") && !url.StartsWith("https://"))
        {
            return true;
        }
        return Regex.IsMatch(url, "[<>\"]");
    }
}
