/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.syncope.core.persistence.jpa.inner;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.syncope.common.lib.wa.GoogleMfaAuthAccount;
import org.apache.syncope.common.lib.wa.GoogleMfaAuthToken;
import org.apache.syncope.common.lib.wa.ImpersonationAccount;
import org.apache.syncope.common.lib.wa.U2FDevice;
import org.apache.syncope.common.lib.wa.WebAuthnDeviceCredential;
import org.apache.syncope.core.persistence.api.dao.AuthProfileDAO;
import org.apache.syncope.core.persistence.api.entity.am.AuthProfile;
import org.apache.syncope.core.persistence.jpa.AbstractTest;
import org.apache.syncope.core.persistence.jpa.entity.am.JPAAuthProfile;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

@Transactional("Master")
public class AuthProfileTest extends AbstractTest {

    @Autowired
    private AuthProfileDAO authProfileDAO;

    @BeforeEach
    public void beforeEach() {
        entityManager().createQuery("DELETE FROM " + JPAAuthProfile.class.getSimpleName()).executeUpdate();
    }

    @Test
    public void googleMfaToken() {
        String id = SecureRandomUtils.generateRandomUUID().toString();

        createAuthProfileWithToken(id, 123456);

        Optional<AuthProfile> result = authProfileDAO.findByOwner(id);
        assertTrue(result.isPresent());

        assertFalse(authProfileDAO.findAll(-1, -1).isEmpty());

        AuthProfile authProfile = result.get();
        result = Optional.ofNullable(authProfileDAO.find(authProfile.getKey()));
        assertTrue(result.isPresent());

        authProfile.setOwner("SyncopeCreate-New");
        authProfile.setGoogleMfaAuthTokens(List.of());
        authProfileDAO.save(authProfile);

        assertFalse(authProfileDAO.findByOwner(id).isPresent());
    }

    @Test
    public void u2fRegisteredDevice() {
        String id = SecureRandomUtils.generateRandomUUID().toString();
        createAuthProfileWithU2FDevice(id, "{ 'record': 1 }");

        Optional<AuthProfile> result = authProfileDAO.findByOwner(id);
        assertTrue(result.isPresent());

        assertFalse(authProfileDAO.findAll(-1, -1).isEmpty());

        AuthProfile authProfile = result.get();
        result = Optional.ofNullable(authProfileDAO.find(authProfile.getKey()));
        assertTrue(result.isPresent());

        authProfile.setOwner("SyncopeCreate-NewU2F");
        authProfile.setU2FRegisteredDevices(List.of());
        authProfileDAO.save(authProfile);

        assertFalse(authProfileDAO.findByOwner(id).isPresent());
    }

    @Test
    public void webAuthnRegisteredDevice() {
        String id = SecureRandomUtils.generateRandomUUID().toString();
        String record = "[ {"
                + "    \"userIdentity\" : {"
                + "      \"name\" : \"casuser\","
                + "      \"displayName\" : \"casuser\""
                + "    },"
                + "    \"credential\" : {"
                + "      \"credentialId\" : \"fFGyV3K5x1\""
                + "    },"
                + "    \"username\" : \"casuser\""
                + "  } ]";

        WebAuthnDeviceCredential credential = new WebAuthnDeviceCredential.Builder().
                json(record).
                identifier("fFGyV3K5x1").
                build();

        createAuthProfileWithWebAuthnDevice(id, List.of(credential));

        Optional<AuthProfile> result = authProfileDAO.findByOwner(id);
        assertTrue(result.isPresent());

        assertFalse(authProfileDAO.findAll(-1, -1).isEmpty());

        AuthProfile authProfile = result.get();
        result = Optional.ofNullable(authProfileDAO.find(authProfile.getKey()));
        assertTrue(result.isPresent());

        authProfile.setOwner("SyncopeCreate-NewU2F");
        authProfile.setWebAuthnDeviceCredentials(List.of());
        authProfileDAO.save(authProfile);

        assertFalse(authProfileDAO.findByOwner(id).isPresent());
    }

    @Test
    public void googleMfaAccount() {
        String id = SecureRandomUtils.generateRandomUUID().toString();

        createAuthProfileWithAccount(id);

        Optional<AuthProfile> result = authProfileDAO.findByOwner(id);
        assertTrue(result.isPresent());

        assertFalse(authProfileDAO.findAll(-1, -1).isEmpty());

        AuthProfile authProfile = result.get();
        result = Optional.ofNullable(authProfileDAO.find(authProfile.getKey()));
        assertTrue(result.isPresent());

        String secret = SecureRandomUtils.generateRandomUUID().toString();
        List<GoogleMfaAuthAccount> googleMfaAuthAccounts = authProfile.getGoogleMfaAuthAccounts();
        assertFalse(googleMfaAuthAccounts.isEmpty());
        GoogleMfaAuthAccount googleMfaAuthAccount = googleMfaAuthAccounts.get(0);
        googleMfaAuthAccount.setSecretKey(secret);

        authProfile.setGoogleMfaAuthAccounts(googleMfaAuthAccounts);
        authProfile = authProfileDAO.save(authProfile);
        assertEquals(secret, authProfile.getGoogleMfaAuthAccounts().get(0).getSecretKey());
    }

    @Test
    public void impersonationAccounts() {
        String id = SecureRandomUtils.generateRandomUUID().toString();

        createAuthProfileWithAccount(id);

        Optional<AuthProfile> result = authProfileDAO.findByOwner(id);
        assertTrue(result.isPresent());

        AuthProfile authProfile = result.get();
        result = Optional.ofNullable(authProfileDAO.find(authProfile.getKey()));
        assertTrue(result.isPresent());

        List<ImpersonationAccount> accounts = IntStream.range(1, 10).
                mapToObj(i -> new ImpersonationAccount.Builder().impersonated("impersonatee" + i).build()).
                collect(Collectors.toList());

        authProfile.setImpersonationAccounts(accounts);
        authProfile = authProfileDAO.save(authProfile);
        assertEquals(accounts.size(), authProfile.getImpersonationAccounts().size());
    }

    private AuthProfile createAuthProfileWithToken(final String owner, final Integer otp) {
        AuthProfile profile = entityFactory.newEntity(AuthProfile.class);
        profile.setOwner(owner);
        GoogleMfaAuthToken token = new GoogleMfaAuthToken.Builder().issueDate(LocalDateTime.now()).token(otp).build();
        profile.setGoogleMfaAuthTokens(List.of(token));
        return authProfileDAO.save(profile);
    }

    private AuthProfile createAuthProfileWithU2FDevice(final String owner, final String record) {
        AuthProfile profile = entityFactory.newEntity(AuthProfile.class);
        profile.setOwner(owner);
        U2FDevice device = new U2FDevice.Builder().issueDate(OffsetDateTime.now()).record(record).build();
        profile.setU2FRegisteredDevices(List.of(device));
        return authProfileDAO.save(profile);
    }

    private AuthProfile createAuthProfileWithWebAuthnDevice(
            final String owner,
            final List<WebAuthnDeviceCredential> credentials) {

        AuthProfile profile = entityFactory.newEntity(AuthProfile.class);
        profile.setOwner(owner);
        profile.setWebAuthnDeviceCredentials(credentials);
        return authProfileDAO.save(profile);
    }

    private AuthProfile createAuthProfileWithAccount(final String owner) {
        AuthProfile profile = entityFactory.newEntity(AuthProfile.class);
        profile.setOwner(owner);
        GoogleMfaAuthAccount account = new GoogleMfaAuthAccount.Builder()
                .registrationDate(OffsetDateTime.now())
                .scratchCodes(List.of(1, 2, 3, 4, 5))
                .secretKey(SecureRandomUtils.generateRandomUUID().toString())
                .validationCode(123456)
                .name(SecureRandomUtils.generateRandomUUID().toString())
                .build();
        profile.setGoogleMfaAuthAccounts(List.of(account));
        return authProfileDAO.save(profile);
    }
}
