package org.apereo.cas.adaptors.x509.authentication.handler.support;

import org.apereo.cas.adaptors.x509.authentication.ExpiredCRLException;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.ThresholdExpiredCRLRevocationPolicy;
import org.apereo.cas.util.DateTimeUtils;
import org.apereo.cas.adaptors.x509.util.MockX509CRL;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import javax.security.auth.x500.X500Principal;
import java.security.GeneralSecurityException;
import java.security.cert.X509CRL;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;


/**
 * Unit test for {@link ThresholdExpiredCRLRevocationPolicy} class.
 *
 * @author Marvin S. Addison
 * @since 3.4.7
 *
 */
@RunWith(Parameterized.class)
public class ThresholdExpiredCRLRevocationPolicyTests {
    /** Policy instance under test. */
    private final ThresholdExpiredCRLRevocationPolicy policy;

    /** CRL to test. */
    private final X509CRL crl;

    /** Expected result of check; null for success */
    private final GeneralSecurityException expected;


    /**
     * Creates a new test instance with given parameters.
     *
     * @param policy Policy to test.
     * @param crl CRL instance to apply policy to.
     * @param expected Expected result of policy application; null to indicate expected success.
     */
    public ThresholdExpiredCRLRevocationPolicyTests(
            final ThresholdExpiredCRLRevocationPolicy policy,
            final X509CRL crl,
            final GeneralSecurityException expected) {

        this.policy = policy;
        this.expected = expected;
        this.crl = crl;
    }

    /**
     * Gets the unit test parameters.
     *
     * @return  Test parameter data.
     * @throws Exception if there is an exception getting the test parameters.
     */
    @Parameters
    public static Collection<Object[]> getTestParameters() throws Exception {
        final Collection<Object[]> params = new ArrayList<>();

        final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
        final ZonedDateTime twoHoursAgo = now.minusHours(2);
        final ZonedDateTime oneHourAgo = now.minusHours(1);
        final ZonedDateTime halfHourAgo = now.minusMinutes(30);
        final X500Principal issuer = new X500Principal("CN=CAS");

        // Test case #1
        // Expect expired for zero leniency on CRL expiring 1ms ago
        final ThresholdExpiredCRLRevocationPolicy zeroThreshold = new ThresholdExpiredCRLRevocationPolicy(0);
        params.add(new Object[] {
                zeroThreshold,
                new MockX509CRL(issuer, DateTimeUtils.dateOf(oneHourAgo), DateTimeUtils.dateOf(now.minusSeconds(1))),
                new ExpiredCRLException("CN=CAS", ZonedDateTime.now(ZoneOffset.UTC)),
        });

        // Test case #2
        // Expect expired for 1h leniency on CRL expired 1 hour 1ms ago
        final ThresholdExpiredCRLRevocationPolicy oneHourThreshold = new ThresholdExpiredCRLRevocationPolicy(3600);
        params.add(new Object[] {
                oneHourThreshold,
                new MockX509CRL(issuer, DateTimeUtils.dateOf(twoHoursAgo), DateTimeUtils.dateOf(oneHourAgo.minusSeconds(1))),
                new ExpiredCRLException("CN=CAS", ZonedDateTime.now(ZoneOffset.UTC)),
        });

        // Test case #3
        // Expect valid for 1h leniency on CRL expired 30m ago
        params.add(new Object[] {
                oneHourThreshold,
                new MockX509CRL(issuer, DateTimeUtils.dateOf(twoHoursAgo), DateTimeUtils.dateOf(halfHourAgo)),
                null,
        });

        return params;
    }

    /**
     * Test method for {@link ThresholdExpiredCRLRevocationPolicy#apply(java.security.cert.X509CRL)}.
     */
    @Test
    public void verifyApply() {
        try {
            this.policy.apply(this.crl);
            if (this.expected != null) {
                Assert.fail("Expected exception of type " + this.expected.getClass());
            }
        } catch (final GeneralSecurityException e) {
            if (this.expected == null) {
                e.printStackTrace();
                Assert.fail("Revocation check failed unexpectedly with exception: " + e);
            } else {
                final Class<?> expectedClass = this.expected.getClass();
                final Class<?> actualClass = e.getClass();
                Assert.assertTrue(
                        String.format("Expected exception of type %s but got %s", expectedClass, actualClass),
                        expectedClass.isAssignableFrom(actualClass));
            }
        }
    }
}
