/*
 *  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.causeway.extensions.secman.applib.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;

import org.jspecify.annotations.Nullable;

import org.apache.causeway.applib.services.appfeat.ApplicationFeatureSort;
import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.io.DataSource;
import org.apache.causeway.commons.io.YamlUtils;
import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermission;
import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode;
import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule;
import org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole;
import org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRoleRepository;
import org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancy;
import org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancyRepository;
import org.apache.causeway.extensions.secman.applib.user.dom.AccountType;
import org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser;
import org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUserRepository;
import org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUserStatus;

import lombok.Data;
import org.jspecify.annotations.NonNull;

/**
 * In-memory model of users, roles and permissions.
 *
 * @since 2.0 {index}
 */
@Data
public class ApplicationSecurityDto {

    @Data
    public static class PermissionDto {

        static PermissionDto from(final ApplicationPermission permission) {
            var permissionDto = new PermissionDto();
            permissionDto.setFeatureFqn(permission.getFeatureFqn());
            permissionDto.setFeatureSort(permission.getFeatureSort());
            permissionDto.setMode(permission.getMode());
            permissionDto.setRule(permission.getRule());
            return permissionDto;
        }

        String featureFqn;
        ApplicationFeatureSort featureSort;
        ApplicationPermissionMode mode;
        ApplicationPermissionRule rule;
    }

    @Data
    public static class RoleDto {

        static RoleDto from(final ApplicationRole role) {
            var roleDto = new RoleDto();
            roleDto.set__name(role.getName());
            roleDto.setDescription(role.getDescription());
            role.getPermissions().stream()
            .map(PermissionDto::from)
            .forEach(roleDto.getPermissions()::add);
            return roleDto;
        }

        String __name; // secondary key - ensure earliest alphabetic order
        String description;
        List<PermissionDto> permissions = new ArrayList<>();
    }

    @Data
    public static class UserDto {

        static UserDto from(final ApplicationUser user, Function<Locale, String> localeStringifier) {
            var userDto = new UserDto();
            userDto.set__username(user.getUsername());
            userDto.setEncryptedPassword(user.getEncryptedPassword());
            userDto.setAccountType(user.getAccountType());
            
            userDto.setAtPath(user.getAtPath());
            userDto.setFamilyName(user.getFamilyName());
            userDto.setGivenName(user.getGivenName());
            userDto.setKnownAs(user.getKnownAs());
            userDto.setEmailAddress(user.getEmailAddress());
            userDto.setPhoneNumber(user.getPhoneNumber());
            userDto.setFaxNumber(user.getFaxNumber());
            userDto.setLanguage(localeStringifier.apply(user.getLanguage()));
            userDto.setNumberFormat(localeStringifier.apply(user.getNumberFormat()));
            userDto.setTimeFormat(localeStringifier.apply(user.getTimeFormat()));
            userDto.setStatus(user.getStatus());

            user.getRoles().stream()
            .map(ApplicationRole::getName)
            .forEach(userDto.getRoleNames()::add);

            return userDto;
        }

        String __username; // secondary key - ensure earliest alphabetic order
        String encryptedPassword;
        AccountType accountType;
        
        String familyName;
        String givenName;
        String knownAs;
        String emailAddress;
        String phoneNumber;
        String faxNumber;
        String language;
        String numberFormat;
        String timeFormat;
        String atPath;
        ApplicationUserStatus status;
        
        List<String> roleNames = new ArrayList<>();
    }

    @Data
    public static class TenancyDto {

        static TenancyDto from(final ApplicationTenancy tenancy) {
            var tenancyDto = new TenancyDto();
            tenancyDto.set__name(tenancy.getName());
            tenancyDto.setPath(tenancy.getPath());

            Optional.ofNullable(tenancy.getParent())
                .ifPresent(parent->
                    tenancyDto.setParentPath(parent.getPath()));
            return tenancyDto;
        }

        String __name; // secondary key - ensure earliest alphabetic order
        String path;
        @Nullable String parentPath;
    }

    public static ApplicationSecurityDto create(
            final @NonNull ApplicationRoleRepository applicationRoleRepository,
            final @NonNull ApplicationUserRepository applicationUserRepository,
            final @NonNull ApplicationTenancyRepository applicationTenancyRepository,
            final @NonNull ValueSemanticsProvider<Locale> localeSemantics) {
        var model = new ApplicationSecurityDto();

        applicationRoleRepository.allRoles().stream()
        .map(RoleDto::from)
        .forEach(model.getRoles()::add);

        applicationUserRepository.allUsers().stream()
        .map(user->UserDto.from(user, 
                locale->localeSemantics.getParser().parseableTextRepresentation(null, locale)))
        .forEach(model.getUsers()::add);

        applicationTenancyRepository.allTenancies().stream()
        .map(TenancyDto::from)
        .forEach(model.getTenancies()::add);

        return model;
    }

    public static Try<ApplicationSecurityDto> tryRead(final @Nullable DataSource dataSource) {
        if(dataSource==null) {
            return Try.success(null);
        }
        return YamlUtils.tryRead(ApplicationSecurityDto.class, dataSource);
    }

    private List<RoleDto> roles = new ArrayList<>();
    private List<UserDto> users = new ArrayList<>();
    private List<TenancyDto> tenancies = new ArrayList<>();

    public String toYaml() {
        return YamlUtils.toStringUtf8(this);
    }

}
