/*
 * 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.service;

import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog;
import com.ctrip.framework.apollo.audit.annotation.OpType;
import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi;
import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI.AppAPI;
import com.ctrip.framework.apollo.portal.component.PortalSettings;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.constant.TracerEventType;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.EnvClusterInfo;
import com.ctrip.framework.apollo.portal.listener.AppCreationEvent;
import com.ctrip.framework.apollo.portal.repository.AppRepository;
import com.ctrip.framework.apollo.portal.spi.UserService;
import com.ctrip.framework.apollo.portal.util.RoleUtils;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.google.common.collect.Lists;
import java.util.Collections;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import org.springframework.util.CollectionUtils;

@Service
public class AppService {

  private final AdminServiceAPI.AppAPI appAPI;
  private final AppRepository appRepository;
  private final ClusterService clusterService;
  private final AppNamespaceService appNamespaceService;
  private final RoleInitializationService roleInitializationService;
  private final RolePermissionService rolePermissionService;
  private final FavoriteService favoriteService;
  private final UserService userService;
  private final ApolloAuditLogApi apolloAuditLogApi;
  private final PortalSettings portalSettings;

  private final ApplicationEventPublisher publisher;

  public AppService(final AppAPI appAPI, final AppRepository appRepository,
      final ClusterService clusterService, final AppNamespaceService appNamespaceService,
      final RoleInitializationService roleInitializationService,
      final RolePermissionService rolePermissionService, final FavoriteService favoriteService,
      final UserService userService, ApplicationEventPublisher publisher,
      final ApolloAuditLogApi apolloAuditLogApi, PortalSettings portalSettings) {
    this.appAPI = appAPI;
    this.appRepository = appRepository;
    this.clusterService = clusterService;
    this.appNamespaceService = appNamespaceService;
    this.roleInitializationService = roleInitializationService;
    this.rolePermissionService = rolePermissionService;
    this.favoriteService = favoriteService;
    this.userService = userService;
    this.apolloAuditLogApi = apolloAuditLogApi;
    this.publisher = publisher;
    this.portalSettings = portalSettings;
  }

  private static void validateOperator(String operator) {
    if (StringUtils.isBlank(operator)) {
      throw new BadRequestException("operator should not be null or empty");
    }
  }

  public List<App> findAll() {
    Iterable<App> apps = appRepository.findAll();

    return Lists.newArrayList(apps);
  }

  public PageDTO<App> findAll(Pageable pageable) {
    Page<App> apps = appRepository.findAll(pageable);

    return new PageDTO<>(apps.getContent(), pageable, apps.getTotalElements());
  }

  public PageDTO<App> searchByAppIdOrAppName(String query, Pageable pageable) {
    Page<App> apps = appRepository.findByAppIdContainingOrNameContaining(query, query, pageable);

    return new PageDTO<>(apps.getContent(), pageable, apps.getTotalElements());
  }

  public List<App> findByAppIds(Set<String> appIds) {
    return appRepository.findByAppIdIn(appIds);
  }

  public List<App> findByAppIds(Set<String> appIds, Pageable pageable) {
    return appRepository.findByAppIdIn(appIds, pageable);
  }

  public List<App> findByOwnerName(String ownerName, Pageable page) {
    return appRepository.findByOwnerName(ownerName, page);
  }

  public App load(String appId) {
    return appRepository.findByAppId(appId);
  }

  public AppDTO load(Env env, String appId) {
    return appAPI.loadApp(env, appId);
  }

  public void createAppInRemote(Env env, App app, String operator) {
    validateOperator(operator);
    if (StringUtils.isBlank(app.getDataChangeCreatedBy())) {
      app.setDataChangeCreatedBy(operator);
      app.setDataChangeLastModifiedBy(operator);
    }

    AppDTO appDTO = BeanUtils.transform(AppDTO.class, app);
    appAPI.createApp(env, appDTO);

    roleInitializationService.initClusterNamespaceRoles(app.getAppId(), env.getName(),
        ConfigConsts.CLUSTER_NAME_DEFAULT, operator);
  }

  private App createAppInLocal(App app, String operator) {
    validateOperator(operator);
    String appId = app.getAppId();
    App managedApp = appRepository.findByAppId(appId);

    if (managedApp != null) {
      throw BadRequestException.appAlreadyExists(appId);
    }

    UserInfo owner = userService.findByUserId(app.getOwnerName());
    if (owner == null) {
      throw new BadRequestException("Application's owner not exist.");
    }
    app.setOwnerEmail(owner.getEmail());

    app.setDataChangeCreatedBy(operator);
    app.setDataChangeLastModifiedBy(operator);

    App createdApp = appRepository.save(app);

    appNamespaceService.createDefaultAppNamespace(appId, operator);
    roleInitializationService.initAppRoles(createdApp);
    List<Env> envs = portalSettings.getActiveEnvs();
    for (Env env : envs) {
      roleInitializationService.initClusterNamespaceRoles(appId, env.getName(),
          ConfigConsts.CLUSTER_NAME_DEFAULT, operator);
    }

    Tracer.logEvent(TracerEventType.CREATE_APP, appId);

    return createdApp;
  }

  @Transactional
  @ApolloAuditLog(type = OpType.CREATE, name = "App.create")
  public App createAppAndAddRolePermission(App app, Set<String> admins, String operator) {
    App createdApp = this.createAppInLocal(app, operator);

    publisher.publishEvent(new AppCreationEvent(createdApp));

    if (!CollectionUtils.isEmpty(admins)) {
      rolePermissionService.assignRoleToUsers(
          RoleUtils.buildAppMasterRoleName(createdApp.getAppId()), admins, operator);
    }

    return createdApp;
  }

  @Transactional
  public App importAppInLocal(App app) {
    String appId = app.getAppId();
    App managedApp = appRepository.findByAppId(appId);

    if (managedApp != null) {
      return app;
    }

    app.setId(0);
    App createdApp = appRepository.save(app);

    roleInitializationService.initAppRoles(createdApp);

    Tracer.logEvent(TracerEventType.CREATE_APP, appId);

    return createdApp;
  }

  @Transactional
  @ApolloAuditLog(type = OpType.UPDATE, name = "App.update")
  public App updateAppInLocal(App app, String operator) {
    validateOperator(operator);
    String appId = app.getAppId();

    App managedApp = appRepository.findByAppId(appId);
    if (managedApp == null) {
      throw BadRequestException.appNotExists(appId);
    }

    managedApp.setName(app.getName());
    managedApp.setOrgId(app.getOrgId());
    managedApp.setOrgName(app.getOrgName());

    String ownerName = app.getOwnerName();
    UserInfo owner = userService.findByUserId(ownerName);
    if (owner == null) {
      throw new BadRequestException("App's owner not exists. owner = %s", ownerName);
    }
    managedApp.setOwnerName(owner.getUserId());
    managedApp.setOwnerEmail(owner.getEmail());

    managedApp.setDataChangeLastModifiedBy(operator);

    return appRepository.save(managedApp);
  }

  public EnvClusterInfo createEnvNavNode(Env env, String appId) {
    EnvClusterInfo node = new EnvClusterInfo(env);
    node.setClusters(clusterService.findClusters(env, appId));
    return node;
  }

  @Transactional
  @ApolloAuditLog(type = OpType.DELETE, name = "App.delete")
  public App deleteAppInLocal(String appId, String operator) {
    validateOperator(operator);
    App managedApp = appRepository.findByAppId(appId);
    if (managedApp == null) {
      throw BadRequestException.appNotExists(appId);
    }
    // this operator is passed to
    // com.ctrip.framework.apollo.portal.listener.DeletionListener.onAppDeletionEvent
    managedApp.setDataChangeLastModifiedBy(operator);

    // 删除portal数据库中的app
    appRepository.deleteApp(appId, operator);

    // append a deleted data influence should be bounded
    apolloAuditLogApi.appendDataInfluences(Collections.singletonList(managedApp), App.class);

    // 删除portal数据库中的appNamespace
    appNamespaceService.batchDeleteByAppId(appId, operator);

    // 删除portal数据库中的收藏表
    favoriteService.batchDeleteByAppId(appId, operator);

    // 删除portal数据库中Permission、Role相关数据
    rolePermissionService.deleteRolePermissionsByAppId(appId, operator);

    return managedApp;
  }
}
