<commit_before>using Certify.Locales;
using Certify.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Certify.Management
{
    public interface ICertifyManager
    {
        Task<bool> IsServerTypeAvailable(StandardServerTypes serverType);

        Task<Version> GetServerTypeVersion(StandardServerTypes serverType);

        Task<bool> LoadSettingsAsync(bool skipIfLoaded);

        Task<ManagedSite> GetManagedSite(string id);

        Task<List<ManagedSite>> GetManagedSites(ManagedSiteFilter filter = null);

        Task<ManagedSite> UpdateManagedSite(ManagedSite site);

        Task DeleteManagedSite(string id);

        List<RegistrationItem> GetContactRegistrations();

        List<IdentifierItem> GetDomainIdentifiers();

        List<CertificateItem> GetCertificates();

        void PerformVaultCleanup();

        bool HasRegisteredContacts();

        Task<APIResult> TestChallenge(ManagedSite managedSite, bool isPreviewMode);

        Task<APIResult> RevokeCertificate(ManagedSite managedSite);

        Task<CertificateRequestResult> PerformDummyCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        Task<bool> AddRegisteredContact(ContactRegistration reg);

        void RemoveExtraContacts(string email);

        void RemoveAllContacts();

        List<SiteBindingItem> GetPrimaryWebSites(bool ignoreStoppedSites);

        string GetAcmeSummary();

        string GetVaultSummary();

        void BeginTrackingProgress(RequestProgressState state);

        Task<CertificateRequestResult> ReapplyCertificateBindings(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        Task<CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        List<DomainOption> GetDomainOptionsFromSite(string siteId);

        List<ManagedSite> ImportManagedSitesFromVault(bool mergeSitesAsSan = false);

        Task<List<CertificateRequestResult>> PerformRenewalAllManagedSites(bool autoRenewalOnly = true, Dictionary<string, Progress<RequestProgressState>> progressTrackers = null);

        Task<List<ManagedSite>> PreviewManagedSites(StandardServerTypes serverType);

        RequestProgressState GetRequestProgressState(string managedSiteId);

        Task<bool> PerformPeriodicTasks();

        event Action<RequestProgressState> OnRequestProgressStateUpdated;

        event Action<ManagedSite> OnManagedSiteUpdated;
    }

    public class CertifyManager : ICertifyManager
    {
        private ItemManager _siteManager = null;
        private IACMEClientProvider _acmeClientProvider = null;
        private IVaultProvider _vaultProvider = null;
        private IISManager _iisManager = null;
        public bool IsSingleInstanceMode { get; set; } = true; //if true we make assumptions about how often to load settings etc
        private ObservableCollection<RequestProgressState> _progressResults { get; set; }

        public event Action<RequestProgressState> OnRequestProgressStateUpdated;

        public event Action<ManagedSite> OnManagedSiteUpdated;

        private bool _isRenewAllInProgress { get; set; }

        public CertifyManager()
        {
            Certify.Management.Util.SetSupportedTLSVersions();

            var acmeSharp = new Certify.Management.APIProviders.ACMESharpProvider();
            // ACME Sharp is both a vault (config storage) provider and ACME client provider
            _acmeClientProvider = acmeSharp;
            _vaultProvider = acmeSharp;
            _siteManager = new ItemManager();
            _iisManager = new IISManager();

            _progressResults = new ObservableCollection<RequestProgressState>();
        }

        public void BeginTrackingProgress(RequestProgressState state)
        {
            var existing = _progressResults.FirstOrDefault(p => p.ManagedItem.Id == state.ManagedItem.Id);
            if (existing != null)
            {
                _progressResults.Remove(existing);
            }
            _progressResults.Add(state);
        }

        public async Task<bool> LoadSettingsAsync(bool skipIfLoaded)
        {
            await _siteManager.LoadAllManagedItems(skipIfLoaded);
            return true;
        }

        public async Task<ManagedSite> GetManagedSite(string id)
        {
            return await _siteManager.GetManagedSite(id);
        }

        public async Task<ManagedSite> UpdateManagedSite(ManagedSite site)
        {
            site = await _siteManager.UpdatedManagedSite(site);

            // report request state to status hub clients
            OnManagedSiteUpdated?.Invoke(site);
            return site;
        }

        public async Task<List<ManagedSite>> GetManagedSites(ManagedSiteFilter filter = null)
        {
            return await this._siteManager.GetManagedSites(filter, true);
        }

        public List<RegistrationItem> GetContactRegistrations()
        {
            return _vaultProvider.GetContactRegistrations();
        }

        public List<IdentifierItem> GetDomainIdentifiers()
        {
            return _vaultProvider.GetDomainIdentifiers();
        }

        public List<CertificateItem> GetCertificates()
        {
            return _vaultProvider.GetCertificates();
        }

        public void PerformVaultCleanup()
        {
            _vaultProvider.PerformVaultCleanup();
        }

        public bool HasRegisteredContacts()
        {
            return _vaultProvider.HasRegisteredContacts();
        }

        /// <summary>
        /// Perform set of test challenges and configuration checks to determine if site appears
        /// valid for certificate requests
        /// </summary>
        /// <param name="managedSite"> managed site to check </param>
        /// <param name="isPreviewMode">
        /// If true, perform full set of checks (DNS etc), if false performs minimal/basic checks
        /// </param>
        /// <returns></returns>
        public async Task<APIResult> TestChallenge(ManagedSite managedSite, bool isPreviewMode)
        {
            return await _vaultProvider.TestChallengeResponse(_iisManager, managedSite, isPreviewMode);
        }

        public async Task<APIResult> RevokeCertificate(ManagedSite managedSite)
        {
            var result = await _vaultProvider.RevokeCertificate(managedSite);
            if (result.IsOK)
            {
                managedSite.CertificateRevoked = true;
            }
            return result;
        }

        /// <summary>
        /// Test dummy method for async UI testing etc 
        /// </summary>
        /// <param name="vaultManager"></param>
        /// <param name="managedSite"></param>
        /// <param name="progress"></param>
        /// <returns></returns>
        public async Task<CertificateRequestResult> PerformDummyCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            return await Task<CertificateRequestResult>.Run<CertificateRequestResult>(async () =>
            {
                for (var i = 0; i < 6; i++)
                {
                    ReportProgress(progress, new RequestProgressState(RequestState.Running, "Step " + i, managedSite));
                    var time = new Random().Next(2000);
                    await Task.Delay(time);
                }

                await Task.Delay(500);

                ReportProgress(progress, new RequestProgressState(RequestState.Success, CoreSR.Finish, managedSite));

                return new CertificateRequestResult { };
            });
        }

        public async Task DeleteManagedSite(string id)
        {
            var site = await _siteManager.GetManagedSite(id);
            if (site != null)
            {
                await this._siteManager.DeleteManagedSite(site);
            }
        }

        public async Task<bool> AddRegisteredContact(ContactRegistration reg)
        {
            // in practise only one registered contact is used, so remove alternatives to avoid cert
            // processing picking up the wrong one
            RemoveAllContacts();

            // now attempt to register the new contact
            if (reg.AgreedToTermsAndConditions)
            {
                // FIXME: not fully async

                return await Task.FromResult(_acmeClientProvider.AddNewRegistrationAndAcceptTOS(reg.EmailAddress));
            }
            else
            {
                // did not agree to terms
                return false;
            }
        }

        /// <summary>
        /// Remove other contacts which don't match the email address given 
        /// </summary>
        /// <param name="email"></param>
        /// <returns></returns>
        public void RemoveExtraContacts(string email)
        {
            var regList = _vaultProvider.GetContactRegistrations();
            foreach (var reg in regList)
            {
                if (!reg.Contacts.Contains("mailto:" + email))
                {
                    _vaultProvider.DeleteContactRegistration(reg.Id);
                }
            }
        }

        public void RemoveAllContacts()
        {
            var regList = _vaultProvider.GetContactRegistrations();
            foreach (var reg in regList)
            {
                _vaultProvider.DeleteContactRegistration(reg.Id);
            }
        }

        public List<SiteBindingItem> GetPrimaryWebSites(bool ignoreStoppedSites)
        {
            return _iisManager.GetPrimarySites(ignoreStoppedSites);
        }

        public string GetAcmeSummary()
        {
            return _acmeClientProvider.GetAcmeBaseURI();
        }

        public string GetVaultSummary()
        {
            return _vaultProvider.GetVaultSummary();
        }

        private void ReportProgress(IProgress<RequestProgressState> progress, RequestProgressState state)
        {
            if (progress != null) progress.Report(state);

            // report request state to staus hub clients
            OnRequestProgressStateUpdated?.Invoke(state);

            if (state.ManagedItem != null)
            {
                LogMessage(state.ManagedItem.Id, state.Message, LogItemType.GeneralWarning);
            }
        }

        private void LogMessage(string managedSiteId, string msg, LogItemType logType = LogItemType.GeneralInfo)
        {
            ManagedSiteLog.AppendLog(managedSiteId, new ManagedSiteLogItem
            {
                EventDate = DateTime.UtcNow,
                LogItemType = LogItemType.GeneralInfo,
                Message = msg
            });
        }

        public async Task<CertificateRequestResult> ReapplyCertificateBindings(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            var result = new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = "" };
            var config = managedSite.RequestConfig;
            var pfxPath = managedSite.CertificatePath;

            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS && config.PerformAutomatedCertBinding)
            {
                ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_AutoBinding, managedSite));

                // Install certificate into certificate store and bind to IIS site
                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                {
                    //all done
                    LogMessage(managedSite.Id, CoreSR.CertifyManager_CompleteRequestAndUpdateBinding, LogItemType.CertificateRequestSuccessful);

                    await UpdateManagedSiteStatus(managedSite, RequestState.Success);

                    result.IsSuccess = true;
                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstalledAndBindingUpdated, config.PrimaryDomain);
                    ReportProgress(progress, new RequestProgressState(RequestState.Success, result.Message, managedSite));
                }
                else
                {
                    // something broke
                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstallFailed, pfxPath);
                    await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);

                    LogMessage(managedSite.Id, result.Message, LogItemType.GeneralError);
                }
            }
            return result;
        }

        public async Task<CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            // FIXME: refactor into different concerns, there's way too much being done here
            if (managedSite.RequestConfig.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && managedSite.RequestConfig.PerformExtensionlessConfigChecks)
            {
                ReportProgress(progress,
                    new RequestProgressState(RequestState.Running, Certify.Locales.CoreSR.CertifyManager_PerformingConfigTests, managedSite)
                );

                var testResult = await TestChallenge(managedSite, isPreviewMode: false);
                if (!testResult.IsOK)
                {
                    return new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = String.Join("; ", testResult.FailedItemSummary), Result = testResult.Result };
                }
            }

            return await Task.Run(async () =>
            {
                // start with a failure result, set to success when succeeding
                var result = new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = "" };

                var config = managedSite.RequestConfig;
                try
                {
                    // run pre-request script, if set
                    if (!string.IsNullOrEmpty(config.PreRequestPowerShellScript))
                    {
                        try
                        {
                            string scriptOutput = await PowerShellManager.RunScript(result, config.PreRequestPowerShellScript);
                            LogMessage(managedSite.Id, $"Pre-Request Script output: \n{scriptOutput}");
                        }
                        catch (Exception ex)
                        {
                            LogMessage(managedSite.Id, $"Pre-Request Script error:\n{ex.Message}");
                        }
                    }

                    // if the script has requested the certificate request to be aborted, skip the request
                    if (result.Abort)
                    {
                        LogMessage(managedSite.Id, $"Certificate Request Aborted: {managedSite.Name}");
                        result.Message = Certify.Locales.CoreSR.CertificateRequestWasAbortedByPSScript;
                        goto CertRequestAborted;
                    }

                    LogMessage(managedSite.Id, $"Beginning Certificate Request Process: {managedSite.Name}");

                    //enable or disable EFS flag on private key certs based on preference
                    if (CoreAppSettings.Current.EnableEFS)
                    {
                        _vaultProvider.EnableSensitiveFileEncryption();
                    }

                    //primary domain and each subject alternative name must now be registered as an identifier with LE and validated
                    ReportProgress(progress,
                        new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_RegisterDomainIdentity, managedSite)
                    );

                    //await Task.Delay(200); //allow UI update, we should we using async calls instead

                    List<string> allDomains = new List<string> { config.PrimaryDomain };

                    if (config.SubjectAlternativeNames != null) allDomains.AddRange(config.SubjectAlternativeNames);

                    // begin by assuming all identifiers are valid
                    bool allIdentifiersValidated = true;

                    if (config.ChallengeType == null) config.ChallengeType = ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP;

                    List<PendingAuthorization> identifierAuthorizations = new List<PendingAuthorization>();
                    var distinctDomains = allDomains.Distinct();

                    string failureSummaryMessage = null;

                    // perform validation process for each domain
                    foreach (var domain in distinctDomains)
                    {
                        //begin authorization process (register identifier, request authorization if not already given)
                        var domainIdentifierId = _vaultProvider.ComputeDomainIdentifierId(domain);

                        LogMessage(managedSite.Id, $"Attempting Domain Validation: {domain}", LogItemType.CertificateRequestStarted);

                        ReportProgress(progress,
                            new RequestProgressState(RequestState.Running, string.Format(Certify.Locales.CoreSR.CertifyManager_RegisteringAndValidatingX0, domain), managedSite)
                            );

                        //TODO: make operations async and yield IO of vault
                        /*var authorization = await Task.Run(() =>
                        {
                            return vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);
                        });*/

                        // begin authorization by registering the domain identifier. This may return
                        // an already validated authorization or we may still have to complete the
                        // authorization challenge. When rate limits are encountered, this step may fail.
                        var authorization = _vaultProvider.BeginRegistrationAndValidation(config, domainIdentifierId, challengeType: config.ChallengeType, domain: domain);

                        if (authorization != null && authorization.Identifier != null)
                        {
                            // check if authorization is pending, it may already be valid if an
                            // existing authorization was reused
                            if (authorization.Identifier.IsAuthorizationPending)
                            {
                                if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                                {
                                    ReportProgress(progress,
                                        new RequestProgressState(
                                            RequestState.Running,
                                            string.Format(Certify.Locales.CoreSR.CertifyManager_PerformingChallengeResponseViaIISX0, domain),
                                            managedSite
                                        )
                                    );

                                    // ask LE to check our answer to their authorization challenge
                                    // (http-01 or tls-sni-01), LE will then attempt to fetch our
                                    // answer, if all accessible and correct (authorized) LE will
                                    // then allow us to request a certificate
                                    authorization = _vaultProvider.PerformIISAutomatedChallengeResponse(_iisManager, managedSite, authorization);

                                    // pass authorization log items onto main log
                                    /*authorization.LogItems?.ForEach((msg) =>
                                    {
                                        if (msg != null) LogMessage(managedSite.Id, msg, LogItemType.GeneralInfo);
                                    });*/

                                    if ((config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && config.PerformExtensionlessConfigChecks && !authorization.ExtensionlessConfigCheckedOK) ||
                                        (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI && config.PerformTlsSniBindingConfigChecks && !authorization.TlsSniConfigCheckedOK))
                                    {
                                        //if we failed the config checks, report any errors
                                        var msg = string.Format(CoreSR.CertifyManager_FailedPrerequisiteCheck, managedSite.ItemType);
                                        LogMessage(managedSite.Id, msg, LogItemType.CertficateRequestFailed);
                                        result.Message = msg;

                                        // TODO: should this be a status save?

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP)
                                        {
                                            result.Message = string.Format(CoreSR.CertifyManager_AutomateConfigurationCheckFailed_HTTP, domain);
                                        }

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI)
                                        {
                                            result.Message = Certify.Locales.CoreSR.CertifyManager_AutomateConfigurationCheckFailed_SNI;
                                        }

                                        ReportProgress(progress, new RequestProgressState(RequestState.Error, result.Message, managedSite) { Result = result });

                                        await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);

                                        break;
                                    }
                                    else
                                    {
                                        ReportProgress(progress, new RequestProgressState(RequestState.Running, string.Format(CoreSR.CertifyManager_ReqestValidationFromLetsEncrypt, domain), managedSite));

                                        try
                                        {
                                            //ask LE to validate our challenge response
                                            _vaultProvider.SubmitChallenge(domainIdentifierId, config.ChallengeType);

                                            bool identifierValidated = _vaultProvider.CompleteIdentifierValidationProcess(authorization.Identifier.Alias);

                                            if (!identifierValidated)
                                            {
                                                var identifierInfo = _vaultProvider.GetDomainIdentifier(domain);
                                                var errorMsg = identifierInfo?.ValidationError;
                                                var errorType = identifierInfo?.ValidationErrorType;

                                                failureSummaryMessage = string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain, errorMsg);
                                                ReportProgress(progress, new RequestProgressState(RequestState.Error, failureSummaryMessage, managedSite));
                                                await UpdateManagedSiteStatus(managedSite, RequestState.Error, failureSummaryMessage);
                                                allIdentifiersValidated = false;
                                            }
                                            else
                                            {
                                                ReportProgress(progress, new RequestProgressState(RequestState.Running, string.Format(CoreSR.CertifyManager_DomainValidationCompleted, domain), managedSite));

                                                identifierAuthorizations.Add(authorization);
                                            }
                                        }
                                        finally
                                        {
                                            // clean up challenge answers
                                            // (.well-known/acme-challenge/* files for http-01 or iis
                                            // bindings for tls-sni-01)
                                            authorization.Cleanup();
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // we already have a completed authorization, check it's valid
                                if (authorization.Identifier.Status == "valid")
                                {
                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationSkipVerifed, domain));

                                    identifierAuthorizations.Add(new PendingAuthorization { Identifier = authorization.Identifier });
                                }
                                else
                                {
                                    var errorMsg = "";
                                    if (authorization?.Identifier != null)
                                    {
                                        errorMsg = authorization.Identifier.ValidationError;
                                        var errorType = authorization.Identifier.ValidationErrorType;
                                    }

                                    failureSummaryMessage = $"Domain validation failed: {domain} \r\n{errorMsg}";

                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain));

                                    allIdentifiersValidated = false;
                                }
                            }
                        }
                        else
                        {
                            // could not begin authorization : TODO: pass error from authorization
                            // step to UI

                            var lastActionLogItem = _vaultProvider.GetLastActionLogItem();
                            var actionLogMsg = "";
                            if (lastActionLogItem != null)
                            {
                                actionLogMsg = lastActionLogItem.ToString();
                            }

                            LogMessage(managedSite.Id, $"Could not begin authorization for domain with Let's Encrypt: { domain } {(authorization?.AuthorizationError != null ? authorization?.AuthorizationError : "Could not register domain identifier")} - {actionLogMsg}");

                            /*if (authorization != null && authorization.LogItems != null)
                            {
                                LogMessage(managedSite.Id, authorization.LogItems);
                            }*/
                            allIdentifiersValidated = false;
                        }

                        // abandon authorization attempts if one of our domains has failed verification
                        if (!allIdentifiersValidated) break;
                    }

                    //check if all identifiers have a valid authorization
                    if (identifierAuthorizations.Count != distinctDomains.Count())
                    {
                        allIdentifiersValidated = false;
                    }

                    if (allIdentifiersValidated)
                    {
                        string primaryDnsIdentifier = identifierAuthorizations.First().Identifier.Alias;
                        string[] alternativeDnsIdentifiers = identifierAuthorizations.Select(i => i.Identifier.Alias).ToArray();

                        ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_RequestCertificate, managedSite));

                        // Perform CSR request
                        // FIXME: make call async
                        var certRequestResult = _vaultProvider.PerformCertificateRequestProcess(primaryDnsIdentifier, alternativeDnsIdentifiers);

                        if (certRequestResult.IsSuccess)
                        {
                            ReportProgress(progress, new RequestProgressState(RequestState.Success, CoreSR.CertifyManager_CompleteRequest, managedSite));

                            string pfxPath = certRequestResult.Result.ToString();

                            // update managed site summary
                            try
                            {
                                var certInfo = CertificateManager.LoadCertificate(pfxPath);
                                managedSite.DateStart = certInfo.NotBefore;
                                managedSite.DateExpiry = certInfo.NotAfter;
                                managedSite.DateRenewed = DateTime.Now;

                                managedSite.CertificatePath = pfxPath;
                                managedSite.CertificateRevoked = false;

                                //ensure certificate contains all the requested domains
                                //var subjectNames = certInfo.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.UpnName, false);

                                //FIXME: LogMessage(managedSite.Id, "New certificate contains following domains: " + subjectNames, LogItemType.GeneralInfo);
                            }
                            catch (Exception)
                            {
                                LogMessage(managedSite.Id, "Failed to parse certificate dates", LogItemType.GeneralError);
                            }

                            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS && config.PerformAutomatedCertBinding)
                            {
                                ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_AutoBinding, managedSite));

                                // Install certificate into certificate store and bind to IIS site
                                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                                {
                                    //all done
                                    LogMessage(managedSite.Id, CoreSR.CertifyManager_CompleteRequestAndUpdateBinding, LogItemType.CertificateRequestSuccessful);

                                    await UpdateManagedSiteStatus(managedSite, RequestState.Success);

                                    result.IsSuccess = true;
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstalledAndBindingUpdated, config.PrimaryDomain);
                                    ReportProgress(progress, new RequestProgressState(RequestState.Success, result.Message, managedSite));
                                }
                                else
                                {
                                    // something broke
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstallFailed, pfxPath);
                                    await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);<commit_msg>Fix: always perform cert binding (there is no manual option in UI)<commit_after>using Certify.Locales;
using Certify.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Certify.Management
{
    public interface ICertifyManager
    {
        Task<bool> IsServerTypeAvailable(StandardServerTypes serverType);

        Task<Version> GetServerTypeVersion(StandardServerTypes serverType);

        Task<bool> LoadSettingsAsync(bool skipIfLoaded);

        Task<ManagedSite> GetManagedSite(string id);

        Task<List<ManagedSite>> GetManagedSites(ManagedSiteFilter filter = null);

        Task<ManagedSite> UpdateManagedSite(ManagedSite site);

        Task DeleteManagedSite(string id);

        List<RegistrationItem> GetContactRegistrations();

        List<IdentifierItem> GetDomainIdentifiers();

        List<CertificateItem> GetCertificates();

        void PerformVaultCleanup();

        bool HasRegisteredContacts();

        Task<APIResult> TestChallenge(ManagedSite managedSite, bool isPreviewMode);

        Task<APIResult> RevokeCertificate(ManagedSite managedSite);

        Task<CertificateRequestResult> PerformDummyCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        Task<bool> AddRegisteredContact(ContactRegistration reg);

        void RemoveExtraContacts(string email);

        void RemoveAllContacts();

        List<SiteBindingItem> GetPrimaryWebSites(bool ignoreStoppedSites);

        string GetAcmeSummary();

        string GetVaultSummary();

        void BeginTrackingProgress(RequestProgressState state);

        Task<CertificateRequestResult> ReapplyCertificateBindings(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        Task<CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null);

        List<DomainOption> GetDomainOptionsFromSite(string siteId);

        List<ManagedSite> ImportManagedSitesFromVault(bool mergeSitesAsSan = false);

        Task<List<CertificateRequestResult>> PerformRenewalAllManagedSites(bool autoRenewalOnly = true, Dictionary<string, Progress<RequestProgressState>> progressTrackers = null);

        Task<List<ManagedSite>> PreviewManagedSites(StandardServerTypes serverType);

        RequestProgressState GetRequestProgressState(string managedSiteId);

        Task<bool> PerformPeriodicTasks();

        event Action<RequestProgressState> OnRequestProgressStateUpdated;

        event Action<ManagedSite> OnManagedSiteUpdated;
    }

    public class CertifyManager : ICertifyManager
    {
        private ItemManager _siteManager = null;
        private IACMEClientProvider _acmeClientProvider = null;
        private IVaultProvider _vaultProvider = null;
        private IISManager _iisManager = null;
        public bool IsSingleInstanceMode { get; set; } = true; //if true we make assumptions about how often to load settings etc
        private ObservableCollection<RequestProgressState> _progressResults { get; set; }

        public event Action<RequestProgressState> OnRequestProgressStateUpdated;

        public event Action<ManagedSite> OnManagedSiteUpdated;

        private bool _isRenewAllInProgress { get; set; }

        public CertifyManager()
        {
            Certify.Management.Util.SetSupportedTLSVersions();

            var acmeSharp = new Certify.Management.APIProviders.ACMESharpProvider();
            // ACME Sharp is both a vault (config storage) provider and ACME client provider
            _acmeClientProvider = acmeSharp;
            _vaultProvider = acmeSharp;
            _siteManager = new ItemManager();
            _iisManager = new IISManager();

            _progressResults = new ObservableCollection<RequestProgressState>();
        }

        public void BeginTrackingProgress(RequestProgressState state)
        {
            var existing = _progressResults.FirstOrDefault(p => p.ManagedItem.Id == state.ManagedItem.Id);
            if (existing != null)
            {
                _progressResults.Remove(existing);
            }
            _progressResults.Add(state);
        }

        public async Task<bool> LoadSettingsAsync(bool skipIfLoaded)
        {
            await _siteManager.LoadAllManagedItems(skipIfLoaded);
            return true;
        }

        public async Task<ManagedSite> GetManagedSite(string id)
        {
            return await _siteManager.GetManagedSite(id);
        }

        public async Task<ManagedSite> UpdateManagedSite(ManagedSite site)
        {
            site = await _siteManager.UpdatedManagedSite(site);

            // report request state to status hub clients
            OnManagedSiteUpdated?.Invoke(site);
            return site;
        }

        public async Task<List<ManagedSite>> GetManagedSites(ManagedSiteFilter filter = null)
        {
            return await this._siteManager.GetManagedSites(filter, true);
        }

        public List<RegistrationItem> GetContactRegistrations()
        {
            return _vaultProvider.GetContactRegistrations();
        }

        public List<IdentifierItem> GetDomainIdentifiers()
        {
            return _vaultProvider.GetDomainIdentifiers();
        }

        public List<CertificateItem> GetCertificates()
        {
            return _vaultProvider.GetCertificates();
        }

        public void PerformVaultCleanup()
        {
            _vaultProvider.PerformVaultCleanup();
        }

        public bool HasRegisteredContacts()
        {
            return _vaultProvider.HasRegisteredContacts();
        }

        /// <summary>
        /// Perform set of test challenges and configuration checks to determine if site appears
        /// valid for certificate requests
        /// </summary>
        /// <param name="managedSite"> managed site to check </param>
        /// <param name="isPreviewMode">
        /// If true, perform full set of checks (DNS etc), if false performs minimal/basic checks
        /// </param>
        /// <returns></returns>
        public async Task<APIResult> TestChallenge(ManagedSite managedSite, bool isPreviewMode)
        {
            return await _vaultProvider.TestChallengeResponse(_iisManager, managedSite, isPreviewMode);
        }

        public async Task<APIResult> RevokeCertificate(ManagedSite managedSite)
        {
            var result = await _vaultProvider.RevokeCertificate(managedSite);
            if (result.IsOK)
            {
                managedSite.CertificateRevoked = true;
            }
            return result;
        }

        /// <summary>
        /// Test dummy method for async UI testing etc 
        /// </summary>
        /// <param name="vaultManager"></param>
        /// <param name="managedSite"></param>
        /// <param name="progress"></param>
        /// <returns></returns>
        public async Task<CertificateRequestResult> PerformDummyCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            return await Task<CertificateRequestResult>.Run<CertificateRequestResult>(async () =>
            {
                for (var i = 0; i < 6; i++)
                {
                    ReportProgress(progress, new RequestProgressState(RequestState.Running, "Step " + i, managedSite));
                    var time = new Random().Next(2000);
                    await Task.Delay(time);
                }

                await Task.Delay(500);

                ReportProgress(progress, new RequestProgressState(RequestState.Success, CoreSR.Finish, managedSite));

                return new CertificateRequestResult { };
            });
        }

        public async Task DeleteManagedSite(string id)
        {
            var site = await _siteManager.GetManagedSite(id);
            if (site != null)
            {
                await this._siteManager.DeleteManagedSite(site);
            }
        }

        public async Task<bool> AddRegisteredContact(ContactRegistration reg)
        {
            // in practise only one registered contact is used, so remove alternatives to avoid cert
            // processing picking up the wrong one
            RemoveAllContacts();

            // now attempt to register the new contact
            if (reg.AgreedToTermsAndConditions)
            {
                // FIXME: not fully async

                return await Task.FromResult(_acmeClientProvider.AddNewRegistrationAndAcceptTOS(reg.EmailAddress));
            }
            else
            {
                // did not agree to terms
                return false;
            }
        }

        /// <summary>
        /// Remove other contacts which don't match the email address given 
        /// </summary>
        /// <param name="email"></param>
        /// <returns></returns>
        public void RemoveExtraContacts(string email)
        {
            var regList = _vaultProvider.GetContactRegistrations();
            foreach (var reg in regList)
            {
                if (!reg.Contacts.Contains("mailto:" + email))
                {
                    _vaultProvider.DeleteContactRegistration(reg.Id);
                }
            }
        }

        public void RemoveAllContacts()
        {
            var regList = _vaultProvider.GetContactRegistrations();
            foreach (var reg in regList)
            {
                _vaultProvider.DeleteContactRegistration(reg.Id);
            }
        }

        public List<SiteBindingItem> GetPrimaryWebSites(bool ignoreStoppedSites)
        {
            return _iisManager.GetPrimarySites(ignoreStoppedSites);
        }

        public string GetAcmeSummary()
        {
            return _acmeClientProvider.GetAcmeBaseURI();
        }

        public string GetVaultSummary()
        {
            return _vaultProvider.GetVaultSummary();
        }

        private void ReportProgress(IProgress<RequestProgressState> progress, RequestProgressState state)
        {
            if (progress != null) progress.Report(state);

            // report request state to staus hub clients
            OnRequestProgressStateUpdated?.Invoke(state);

            if (state.ManagedItem != null)
            {
                LogMessage(state.ManagedItem.Id, state.Message, LogItemType.GeneralWarning);
            }
        }

        private void LogMessage(string managedSiteId, string msg, LogItemType logType = LogItemType.GeneralInfo)
        {
            ManagedSiteLog.AppendLog(managedSiteId, new ManagedSiteLogItem
            {
                EventDate = DateTime.UtcNow,
                LogItemType = LogItemType.GeneralInfo,
                Message = msg
            });
        }

        public async Task<CertificateRequestResult> ReapplyCertificateBindings(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            var result = new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = "" };
            var config = managedSite.RequestConfig;
            var pfxPath = managedSite.CertificatePath;

            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
            {
                ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_AutoBinding, managedSite));

                // Install certificate into certificate store and bind to IIS site
                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                {
                    //all done
                    LogMessage(managedSite.Id, CoreSR.CertifyManager_CompleteRequestAndUpdateBinding, LogItemType.CertificateRequestSuccessful);

                    await UpdateManagedSiteStatus(managedSite, RequestState.Success);

                    result.IsSuccess = true;
                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstalledAndBindingUpdated, config.PrimaryDomain);
                    ReportProgress(progress, new RequestProgressState(RequestState.Success, result.Message, managedSite));
                }
                else
                {
                    // something broke
                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstallFailed, pfxPath);
                    await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);

                    LogMessage(managedSite.Id, result.Message, LogItemType.GeneralError);
                }
            }
            return result;
        }

        public async Task<CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress<RequestProgressState> progress = null)
        {
            // FIXME: refactor into different concerns, there's way too much being done here
            if (managedSite.RequestConfig.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && managedSite.RequestConfig.PerformExtensionlessConfigChecks)
            {
                ReportProgress(progress,
                    new RequestProgressState(RequestState.Running, Certify.Locales.CoreSR.CertifyManager_PerformingConfigTests, managedSite)
                );

                var testResult = await TestChallenge(managedSite, isPreviewMode: false);
                if (!testResult.IsOK)
                {
                    return new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = String.Join("; ", testResult.FailedItemSummary), Result = testResult.Result };
                }
            }

            return await Task.Run(async () =>
            {
                // start with a failure result, set to success when succeeding
                var result = new CertificateRequestResult { ManagedItem = managedSite, IsSuccess = false, Message = "" };

                var config = managedSite.RequestConfig;
                try
                {
                    // run pre-request script, if set
                    if (!string.IsNullOrEmpty(config.PreRequestPowerShellScript))
                    {
                        try
                        {
                            string scriptOutput = await PowerShellManager.RunScript(result, config.PreRequestPowerShellScript);
                            LogMessage(managedSite.Id, $"Pre-Request Script output: \n{scriptOutput}");
                        }
                        catch (Exception ex)
                        {
                            LogMessage(managedSite.Id, $"Pre-Request Script error:\n{ex.Message}");
                        }
                    }

                    // if the script has requested the certificate request to be aborted, skip the request
                    if (result.Abort)
                    {
                        LogMessage(managedSite.Id, $"Certificate Request Aborted: {managedSite.Name}");
                        result.Message = Certify.Locales.CoreSR.CertificateRequestWasAbortedByPSScript;
                        goto CertRequestAborted;
                    }

                    LogMessage(managedSite.Id, $"Beginning Certificate Request Process: {managedSite.Name}");

                    //enable or disable EFS flag on private key certs based on preference
                    if (CoreAppSettings.Current.EnableEFS)
                    {
                        _vaultProvider.EnableSensitiveFileEncryption();
                    }

                    //primary domain and each subject alternative name must now be registered as an identifier with LE and validated
                    ReportProgress(progress,
                        new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_RegisterDomainIdentity, managedSite)
                    );

                    //await Task.Delay(200); //allow UI update, we should we using async calls instead

                    List<string> allDomains = new List<string> { config.PrimaryDomain };

                    if (config.SubjectAlternativeNames != null) allDomains.AddRange(config.SubjectAlternativeNames);

                    // begin by assuming all identifiers are valid
                    bool allIdentifiersValidated = true;

                    if (config.ChallengeType == null) config.ChallengeType = ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP;

                    List<PendingAuthorization> identifierAuthorizations = new List<PendingAuthorization>();
                    var distinctDomains = allDomains.Distinct();

                    string failureSummaryMessage = null;

                    // perform validation process for each domain
                    foreach (var domain in distinctDomains)
                    {
                        //begin authorization process (register identifier, request authorization if not already given)
                        var domainIdentifierId = _vaultProvider.ComputeDomainIdentifierId(domain);

                        LogMessage(managedSite.Id, $"Attempting Domain Validation: {domain}", LogItemType.CertificateRequestStarted);

                        ReportProgress(progress,
                            new RequestProgressState(RequestState.Running, string.Format(Certify.Locales.CoreSR.CertifyManager_RegisteringAndValidatingX0, domain), managedSite)
                            );

                        //TODO: make operations async and yield IO of vault
                        /*var authorization = await Task.Run(() =>
                        {
                            return vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);
                        });*/

                        // begin authorization by registering the domain identifier. This may return
                        // an already validated authorization or we may still have to complete the
                        // authorization challenge. When rate limits are encountered, this step may fail.
                        var authorization = _vaultProvider.BeginRegistrationAndValidation(config, domainIdentifierId, challengeType: config.ChallengeType, domain: domain);

                        if (authorization != null && authorization.Identifier != null)
                        {
                            // check if authorization is pending, it may already be valid if an
                            // existing authorization was reused
                            if (authorization.Identifier.IsAuthorizationPending)
                            {
                                if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                                {
                                    ReportProgress(progress,
                                        new RequestProgressState(
                                            RequestState.Running,
                                            string.Format(Certify.Locales.CoreSR.CertifyManager_PerformingChallengeResponseViaIISX0, domain),
                                            managedSite
                                        )
                                    );

                                    // ask LE to check our answer to their authorization challenge
                                    // (http-01 or tls-sni-01), LE will then attempt to fetch our
                                    // answer, if all accessible and correct (authorized) LE will
                                    // then allow us to request a certificate
                                    authorization = _vaultProvider.PerformIISAutomatedChallengeResponse(_iisManager, managedSite, authorization);

                                    // pass authorization log items onto main log
                                    /*authorization.LogItems?.ForEach((msg) =>
                                    {
                                        if (msg != null) LogMessage(managedSite.Id, msg, LogItemType.GeneralInfo);
                                    });*/

                                    if ((config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && config.PerformExtensionlessConfigChecks && !authorization.ExtensionlessConfigCheckedOK) ||
                                        (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI && config.PerformTlsSniBindingConfigChecks && !authorization.TlsSniConfigCheckedOK))
                                    {
                                        //if we failed the config checks, report any errors
                                        var msg = string.Format(CoreSR.CertifyManager_FailedPrerequisiteCheck, managedSite.ItemType);
                                        LogMessage(managedSite.Id, msg, LogItemType.CertficateRequestFailed);
                                        result.Message = msg;

                                        // TODO: should this be a status save?

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP)
                                        {
                                            result.Message = string.Format(CoreSR.CertifyManager_AutomateConfigurationCheckFailed_HTTP, domain);
                                        }

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI)
                                        {
                                            result.Message = Certify.Locales.CoreSR.CertifyManager_AutomateConfigurationCheckFailed_SNI;
                                        }

                                        ReportProgress(progress, new RequestProgressState(RequestState.Error, result.Message, managedSite) { Result = result });

                                        await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);

                                        break;
                                    }
                                    else
                                    {
                                        ReportProgress(progress, new RequestProgressState(RequestState.Running, string.Format(CoreSR.CertifyManager_ReqestValidationFromLetsEncrypt, domain), managedSite));

                                        try
                                        {
                                            //ask LE to validate our challenge response
                                            _vaultProvider.SubmitChallenge(domainIdentifierId, config.ChallengeType);

                                            bool identifierValidated = _vaultProvider.CompleteIdentifierValidationProcess(authorization.Identifier.Alias);

                                            if (!identifierValidated)
                                            {
                                                var identifierInfo = _vaultProvider.GetDomainIdentifier(domain);
                                                var errorMsg = identifierInfo?.ValidationError;
                                                var errorType = identifierInfo?.ValidationErrorType;

                                                failureSummaryMessage = string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain, errorMsg);
                                                ReportProgress(progress, new RequestProgressState(RequestState.Error, failureSummaryMessage, managedSite));
                                                await UpdateManagedSiteStatus(managedSite, RequestState.Error, failureSummaryMessage);
                                                allIdentifiersValidated = false;
                                            }
                                            else
                                            {
                                                ReportProgress(progress, new RequestProgressState(RequestState.Running, string.Format(CoreSR.CertifyManager_DomainValidationCompleted, domain), managedSite));

                                                identifierAuthorizations.Add(authorization);
                                            }
                                        }
                                        finally
                                        {
                                            // clean up challenge answers
                                            // (.well-known/acme-challenge/* files for http-01 or iis
                                            // bindings for tls-sni-01)
                                            authorization.Cleanup();
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // we already have a completed authorization, check it's valid
                                if (authorization.Identifier.Status == "valid")
                                {
                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationSkipVerifed, domain));

                                    identifierAuthorizations.Add(new PendingAuthorization { Identifier = authorization.Identifier });
                                }
                                else
                                {
                                    var errorMsg = "";
                                    if (authorization?.Identifier != null)
                                    {
                                        errorMsg = authorization.Identifier.ValidationError;
                                        var errorType = authorization.Identifier.ValidationErrorType;
                                    }

                                    failureSummaryMessage = $"Domain validation failed: {domain} \r\n{errorMsg}";

                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain));

                                    allIdentifiersValidated = false;
                                }
                            }
                        }
                        else
                        {
                            // could not begin authorization : TODO: pass error from authorization
                            // step to UI

                            var lastActionLogItem = _vaultProvider.GetLastActionLogItem();
                            var actionLogMsg = "";
                            if (lastActionLogItem != null)
                            {
                                actionLogMsg = lastActionLogItem.ToString();
                            }

                            LogMessage(managedSite.Id, $"Could not begin authorization for domain with Let's Encrypt: { domain } {(authorization?.AuthorizationError != null ? authorization?.AuthorizationError : "Could not register domain identifier")} - {actionLogMsg}");

                            /*if (authorization != null && authorization.LogItems != null)
                            {
                                LogMessage(managedSite.Id, authorization.LogItems);
                            }*/
                            allIdentifiersValidated = false;
                        }

                        // abandon authorization attempts if one of our domains has failed verification
                        if (!allIdentifiersValidated) break;
                    }

                    //check if all identifiers have a valid authorization
                    if (identifierAuthorizations.Count != distinctDomains.Count())
                    {
                        allIdentifiersValidated = false;
                    }

                    if (allIdentifiersValidated)
                    {
                        string primaryDnsIdentifier = identifierAuthorizations.First().Identifier.Alias;
                        string[] alternativeDnsIdentifiers = identifierAuthorizations.Select(i => i.Identifier.Alias).ToArray();

                        ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_RequestCertificate, managedSite));

                        // Perform CSR request
                        // FIXME: make call async
                        var certRequestResult = _vaultProvider.PerformCertificateRequestProcess(primaryDnsIdentifier, alternativeDnsIdentifiers);

                        if (certRequestResult.IsSuccess)
                        {
                            ReportProgress(progress, new RequestProgressState(RequestState.Success, CoreSR.CertifyManager_CompleteRequest, managedSite));

                            string pfxPath = certRequestResult.Result.ToString();

                            // update managed site summary
                            try
                            {
                                var certInfo = CertificateManager.LoadCertificate(pfxPath);
                                managedSite.DateStart = certInfo.NotBefore;
                                managedSite.DateExpiry = certInfo.NotAfter;
                                managedSite.DateRenewed = DateTime.Now;

                                managedSite.CertificatePath = pfxPath;
                                managedSite.CertificateRevoked = false;

                                //ensure certificate contains all the requested domains
                                //var subjectNames = certInfo.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.UpnName, false);

                                //FIXME: LogMessage(managedSite.Id, "New certificate contains following domains: " + subjectNames, LogItemType.GeneralInfo);
                            }
                            catch (Exception)
                            {
                                LogMessage(managedSite.Id, "Failed to parse certificate dates", LogItemType.GeneralError);
                            }

                            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                            {
                                ReportProgress(progress, new RequestProgressState(RequestState.Running, CoreSR.CertifyManager_AutoBinding, managedSite));

                                // Install certificate into certificate store and bind to IIS site
                                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                                {
                                    //all done
                                    LogMessage(managedSite.Id, CoreSR.CertifyManager_CompleteRequestAndUpdateBinding, LogItemType.CertificateRequestSuccessful);

                                    await UpdateManagedSiteStatus(managedSite, RequestState.Success);

                                    result.IsSuccess = true;
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstalledAndBindingUpdated, config.PrimaryDomain);
                                    ReportProgress(progress, new RequestProgressState(RequestState.Success, result.Message, managedSite));
                                }
                                else
                                {
                                    // something broke
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstallFailed, pfxPath);
                                    await UpdateManagedSiteStatus(managedSite, RequestState.Error, result.Message);
{'fraction_non_alphanumeric': 0.06468879993721058, 'fraction_numerical': 0.0008790518797582608, 'mean_word_length': 1.4978826850690088, 'pattern_counts': {'":': 0, '<': 173, '<?xml version=': 0, '>': 183, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 264, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6336404', 'n_tokens_mistral': 13447, 'n_tokens_neox': 12661, 'n_words': 2912}