/*
 * Copyright 2025 Apollo Authors
 *
 * Licensed 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 com.ctrip.framework.apollo.portal.component;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.portal.constant.PermissionType;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.po.Permission;
import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
import com.ctrip.framework.apollo.portal.service.RolePermissionService;
import com.ctrip.framework.apollo.portal.service.SystemRoleManagerService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class UserPermissionValidatorTest {

  private static final String USER_ID = "test-user";
  private static final String APP_ID = "test-app";
  private static final String ENV = "DEV";
  private static final String CLUSTER = "default";
  private static final String NAMESPACE = "application";
  @Mock
  private UserInfoHolder userInfoHolder;
  @Mock
  private RolePermissionService rolePermissionService;
  @Mock
  private PortalConfig portalConfig;
  @Mock
  private AppNamespaceService appNamespaceService;
  @Mock
  private SystemRoleManagerService systemRoleManagerService;
  @InjectMocks
  private UserPermissionValidator validator;

  @BeforeEach
  void setUp() {
    // Create a UserInfo instance
    UserInfo stubUser = new UserInfo();
    stubUser.setUserId(USER_ID);
    stubUser.setName("test");
    lenient().when(userInfoHolder.getUser()).thenReturn(stubUser);
  }

  // 1. hasCreateAppNamespacePermission tests

  @Test
  void hasCreateAppNamespacePermission_publicNamespace() {
    AppNamespace publicNs = new AppNamespace();
    publicNs.setPublic(true);
    List<Permission> requiredPermissions =
        Collections.singletonList(new Permission(PermissionType.CREATE_NAMESPACE, APP_ID));
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPermissions)).thenReturn(true);
    assertThat(validator.hasCreateAppNamespacePermission(APP_ID, publicNs)).isTrue();
  }

  @Test
  void hasCreateAppNamespacePermission_privateNamespace_adminCanCreate() {
    AppNamespace privateNs = new AppNamespace();
    privateNs.setPublic(false);

    when(portalConfig.canAppAdminCreatePrivateNamespace()).thenReturn(true);
    List<Permission> requiredPermissions =
        Collections.singletonList(new Permission(PermissionType.CREATE_NAMESPACE, APP_ID));
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPermissions)).thenReturn(true);

    assertThat(validator.hasCreateAppNamespacePermission(APP_ID, privateNs)).isTrue();
  }

  @Test
  void hasCreateAppNamespacePermission_privateNamespace_adminCannotCreate_andUserIsSuperAdmin() {
    AppNamespace privateNs = new AppNamespace();
    privateNs.setPublic(false);

    when(portalConfig.canAppAdminCreatePrivateNamespace()).thenReturn(false);
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(true);

    assertThat(validator.hasCreateAppNamespacePermission(APP_ID, privateNs)).isTrue();
  }

  @Test
  void hasCreateAppNamespacePermission_privateNamespace_adminCannotCreate_andUserIsNotSuperAdmin() {
    AppNamespace privateNs = new AppNamespace();
    privateNs.setPublic(false);

    when(portalConfig.canAppAdminCreatePrivateNamespace()).thenReturn(false);
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);

    assertThat(validator.hasCreateAppNamespacePermission(APP_ID, privateNs)).isFalse();
  }

  // 2. isSuperAdmin tests

  @Test
  void isSuperAdmin_true() {
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(true);
    assertThat(validator.isSuperAdmin()).isTrue();
  }

  @Test
  void isSuperAdmin_false() {
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);
    assertThat(validator.isSuperAdmin()).isFalse();
  }

  @Test
  void shouldHideConfigToCurrentUser_publicNamespace() {
    when(portalConfig.isConfigViewMemberOnly(ENV)).thenReturn(true);

    AppNamespace publicNs = new AppNamespace();
    publicNs.setPublic(true);
    when(appNamespaceService.findByAppIdAndName(APP_ID, NAMESPACE)).thenReturn(publicNs);

    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, ENV, CLUSTER, NAMESPACE)).isFalse();
  }

  @Test
  void shouldHideConfigToCurrentUser_userIsNotAppAdmin() {
    when(portalConfig.isConfigViewMemberOnly(ENV)).thenReturn(true);
    when(appNamespaceService.findByAppIdAndName(APP_ID, NAMESPACE)).thenReturn(null);
    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, ENV, CLUSTER, NAMESPACE)).isTrue();
  }

  @Test
  void shouldHideConfigToCurrentUser_configViewNotMemberOnly() {
    when(portalConfig.isConfigViewMemberOnly(ENV)).thenReturn(false);
    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, ENV, CLUSTER, NAMESPACE)).isFalse();
  }

  // 4. hasCreateApplicationPermission tests

  @Test
  void hasCreateApplicationPermission_true() {
    when(systemRoleManagerService.hasCreateApplicationPermission(USER_ID)).thenReturn(true);
    assertThat(validator.hasCreateApplicationPermission()).isTrue();
  }

  @Test
  void hasCreateApplicationPermission_false() {
    when(systemRoleManagerService.hasCreateApplicationPermission(USER_ID)).thenReturn(false);
    assertThat(validator.hasCreateApplicationPermission()).isFalse();
  }

  @Test
  void hasManageUsersPermission_delegatesToSystemRoleManagerService() {
    when(systemRoleManagerService.hasManageUsersPermission(USER_ID)).thenReturn(true);

    assertThat(validator.hasManageUsersPermission()).isTrue();
  }

  // 5. hasManageAppMasterPermission tests

  @Test
  void hasManageAppMasterPermission_superAdmin() {
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(true);
    assertThat(validator.hasManageAppMasterPermission(APP_ID)).isTrue();
  }

  @Test
  void hasManageAppMasterPermission_normalUser_withAssignRole_andManageAppMaster() {
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);
    List<Permission> requiredPermissions = Collections.singletonList(
        new Permission(PermissionType.ASSIGN_ROLE, APP_ID));
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPermissions)).thenReturn(true);

    when(systemRoleManagerService.hasManageAppMasterPermission(USER_ID, APP_ID)).thenReturn(true);

    assertThat(validator.hasManageAppMasterPermission(APP_ID)).isTrue();
  }

  @Test
  void hasManageAppMasterPermission_normalUser_withoutManageAppMaster() {
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);
    List<Permission> requiredPermissions = Collections.singletonList(
            new Permission(PermissionType.ASSIGN_ROLE, APP_ID));
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPermissions)).thenReturn(true);
    when(systemRoleManagerService.hasManageAppMasterPermission(USER_ID, APP_ID)).thenReturn(false);

    assertThat(validator.hasManageAppMasterPermission(APP_ID)).isFalse();
  }


  @Test
  void hasPermissions_match() {
    List<Permission> requiredPerms = Lists.newArrayList(new Permission(), new Permission());
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPerms)).thenReturn(true);

    assertThat(validator.hasPermissions(requiredPerms)).isTrue();
  }

  @Test
  void hasPermissions_notMatch() {
    List<Permission> requiredPerms = Lists.newArrayList(new Permission(), new Permission());
    when(rolePermissionService.hasAnyPermission(USER_ID, requiredPerms)).thenReturn(false);

    assertThat(validator.hasPermissions(requiredPerms)).isFalse();
  }

  @Test
  void hasReleaseNamespacePermission_match() {
    when(rolePermissionService.hasAnyPermission(eq(USER_ID), anyList())).thenReturn(true);

    assertThat(validator.hasReleaseNamespacePermission(APP_ID, ENV, CLUSTER, NAMESPACE)).isTrue();
  }

  @Test
  void hasPermissions_emptyList() {
    assertThat(validator.hasPermissions(Collections.emptyList())).isFalse();
  }

  /**
   * Test that shouldHideConfigToCurrentUser normalizes env names correctly.
   * Verifies fix for issue #5442 where env aliases (prod/PROD/PRO) caused permission bypass.
   *
   * @see <a href="https://github.com/apolloconfig/apollo/issues/5442">#5442</a>
   */
  @Test
  void shouldHideConfigToCurrentUser_envNormalization_prodAlias() {
    // Setup: config view is member-only for "PRO" (canonical form)
    when(portalConfig.isConfigViewMemberOnly("PRO")).thenReturn(true);
    when(appNamespaceService.findByAppIdAndName(APP_ID, NAMESPACE)).thenReturn(null);

    // User is not app admin and has no operate permission
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);
    when(rolePermissionService.hasAnyPermission(eq(USER_ID), anyList())).thenReturn(false);

    // Test with "prod" alias - should normalize to "PRO" and hide config
    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, "prod", CLUSTER, NAMESPACE)).isTrue();

    // Test with "PROD" alias - should also normalize to "PRO" and hide config
    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, "PROD", CLUSTER, NAMESPACE)).isTrue();
  }

  @Test
  void shouldHideConfigToCurrentUser_envNormalization_localAlias() {
    // Setup: config view is member-only for "LOCAL" (canonical form)
    when(portalConfig.isConfigViewMemberOnly("LOCAL")).thenReturn(true);
    when(appNamespaceService.findByAppIdAndName(APP_ID, NAMESPACE)).thenReturn(null);

    // User is not app admin and has no operate permission
    when(rolePermissionService.isSuperAdmin(USER_ID)).thenReturn(false);
    when(rolePermissionService.hasAnyPermission(eq(USER_ID), anyList())).thenReturn(false);

    // Test with "local" alias - should normalize to "LOCAL" and hide config
    assertThat(validator.shouldHideConfigToCurrentUser(APP_ID, "local", CLUSTER, NAMESPACE)).isTrue();
  }

}
