using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using Autofac;
using FitConnect;
using FitConnect.Encryption;
using FitConnect.Models;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using MockContainer;
using Moq;
using NUnit.Framework;

namespace IntegrationTests;

[TestFixture]
public class CertificateValidation {
    private MockSettings _settings = null!;
    private ILogger _logger = null!;
    private CertificateHelper _certificateHelper = null!;

    [SetUp]
    public void Setup() {
        var container = Container.Create();
        _settings = container.Resolve<MockSettings>();

        _logger = LoggerFactory.Create(
            builder => {
                builder.AddConsole();
                builder.SetMinimumLevel(LogLevel.Debug);
            }).CreateLogger("E2E Test");


        _certificateHelper = new CertificateHelper(_logger);
    }

    [Test]
    [Ignore("No credentials for dev environment")]
    public void CheckCertificateInEnvironment_Dev() {
        var environment = FitConnectEnvironment.Develop;
        var sender = Client.GetSender(environment, _settings.SenderClientId,
            _settings.SenderClientSecret,
            _logger);

        var certificate = (sender as FitConnect.Sender)!
            .GetPublicKeyFromDestination(_settings.DestinationId).Result;
        new CertificateHelper(_logger).ValidateCertificate(JsonWebKey.Create(certificate),
            LogLevel.Trace);
    }

    [Test]
    public void CheckCertificateInEnvironment_Testing() {
        var environment = FitConnectEnvironment.Testing;
        var sender = Client.GetSender(environment, _settings.SenderClientId,
            _settings.SenderClientSecret,
            _logger);

        var certificate = (sender as FitConnect.Sender)!
            .GetPublicKeyFromDestination(_settings.DestinationId).Result;
        new CertificateHelper(_logger).ValidateCertificate(JsonWebKey.Create(certificate),
            LogLevel.Trace);
    }


    [Test]
    [Ignore("No credentials for staging environment")]
    public void CheckCertificateInEnvironment_Staging() {
        var environment = FitConnectEnvironment.Staging;
        var sender = Client.GetSender(environment, _settings.SenderClientId,
            _settings.SenderClientSecret,
            _logger);

        Assert.Throws<AggregateException>(() => {
            sender.WithDestination(_settings.DestinationId)
                .WithServiceType("", _settings.LeikaKey)
                .WithAttachments(new Attachment("Test.pdf", "Simple Test PDF"))
                .Submit();
        })!.InnerExceptions.Any(e => e.GetType() == typeof(SecurityException)).Should().BeTrue();
    }

    [Test]
    [Ignore("No credentials for production environment")]
    public void CheckCertificateInEnvironment_Production() {
        var environment = FitConnectEnvironment.Production;
        var sender = Client.GetSender(environment, _settings.SenderClientId,
            _settings.SenderClientSecret,
            _logger);

        Assert.Throws<AggregateException>(() => {
            sender.WithDestination(_settings.DestinationId)
                .WithServiceType("", _settings.LeikaKey)
                .WithAttachments(new Attachment("Test.pdf", "Simple Test PDF"))
                .Submit();
        })!.InnerExceptions.Any(e => e.GetType() == typeof(SecurityException)).Should().BeTrue();
    }

    [Test]
    public void CheckPublicKeyEncryption() {
        _certificateHelper.ValidateCertificate(new JsonWebKey(_settings.PublicKeyEncryption))
            .Should().BeFalse();
    }

    [Test]
    public void CheckPublicKeySignature() {
        _certificateHelper
            .ValidateCertificate(new JsonWebKey(_settings.PublicKeySignatureVerification))
            .Should().BeFalse();
    }

    [Test]
    public void CheckPrivateKeyDecryption() {
        _certificateHelper.ValidateCertificate(new JsonWebKey(_settings.PrivateKeyDecryption))
            .Should().BeTrue();
    }

    [Test]
    public void CheckSetPublicKey() {
        _certificateHelper.ValidateCertificate(new JsonWebKey(_settings.SetPublicKeys))
            .Should().BeTrue();
    }

    [Test]
    public void CheckPrivateKeySigning() {
        _certificateHelper.ValidateCertificate(new JsonWebKey(_settings.PrivateKeySigning))
            .Should().BeTrue();
    }

    [Ignore("Not the scope of this branch - reactivate later")]
    [Test]
    public void CheckPemFiles() {
        var files = Directory.GetFiles("./certificates");
        var success = 0;
        var failed = 0;
        var failedCerts = new List<string>();

        foreach (var fileName in files) {
            _logger.LogInformation("Checking file: {FileName}", fileName);

            var certificateContents = Directory.GetFiles("./certificates/roots");
            var rootCertificates = certificateContents
                .Select(file => new X509Certificate2(file)).ToArray();
            certificateContents.Length.Should().Be(rootCertificates.Length);


            if (fileName.EndsWith(".json")) {
                var shouldFail = !fileName.Contains("/valid");
                var jwk = new JsonWebKey(File.ReadAllText(fileName));
                var valid = _certificateHelper.ValidateCertificate(jwk,
                    shouldFail ? LogLevel.Warning : LogLevel.Critical, rootCertificates
                );

                if (shouldFail)
                    valid = !valid;

                if (valid) {
                    success++;
                }
                else {
                    failed++;
                    failedCerts.Add(fileName);
                }
            }
            else {
                var certificate = new X509Certificate2(fileName);
                var valid = _certificateHelper.ValidateCertificate(certificate, out var states,
                    null);
                if (valid) {
                    success++;
                }
                else {
                    failed++;
                    failedCerts.Add(fileName);
                }
            }
        }

        _logger.LogWarning("Failed certificates: {Certs}",
            failedCerts.Aggregate("\n", (a, b) => a + "\t - " + b + "\n"));
        _logger.LogInformation("Success: {Success}, Failed: {Failed}", success, failed);
        failed.Should().Be(0);
    }
}