/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
 * 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.
 */

/* instrument ignore file */
import { HashMap } from '@kit.ArkTS';
import bundleManager from '@ohos.bundle.bundleManager';
import usageStatistics from '@ohos.resourceschedule.usageStatistics';
import { AppEntryChangedListener, AppListLoader } from '@ohos/settings.application/src/main/ets/AppListLoader';
import { AppEntry } from '@ohos/settings.application/src/main/ets/AppModel';
import { AppUtils } from '@ohos/settings.application/src/main/ets/AppUtils';
import { AppCloneBadgeComponent } from '@ohos/settings.application/src/main/ets/components/AppCloneBadgeComponent';
import {
  APP_LOADING_DONE_EVENT,
  EVENT_ID_BUNDLE_RESOURCES_CHANGED,
  STORAGE_APP_CHANGE_EVENT
} from '@ohos/settings.common/src/main/ets/event/types';
import { EventBus } from '@ohos/settings.common/src/main/ets/framework/common/EventBus';
import { PageRouter } from '@ohos/settings.common/src/main/ets/framework/common/PageRouter';
import { OrderedDataSource } from '@ohos/settings.common/src/main/ets/framework/model/OrderedDataSource';
import { CompCtrlParam, ComponentControl } from '@ohos/settings.common/src/main/ets/framework/model/SettingBaseModel';
import {
  ComparableSettingItemModel,
  ItemResultType,
  ItemType,
  SettingCheckboxModel,
  SettingCheckboxStyle,
  SettingIconStyle,
  SettingIconType,
  SettingItemModel
} from '@ohos/settings.common/src/main/ets/framework/model/SettingItemModel';
import { AccessibilityUtils } from '@ohos/settings.common/src/main/ets/utils/AccessibilityUtils';
import { CheckEmptyUtils } from '@ohos/settings.common/src/main/ets/utils/CheckEmptyUtils';
import { LogUtil } from '@ohos/settings.common/src/main/ets/utils/LogUtil';
import { ResourceUtil } from '@ohos/settings.common/src/main/ets/utils/ResourceUtil';
import { AppEntryStorage } from '../constant/StorageConstant';
import { StorageUtil } from '../utils/StorageUtil';

const TAG: string = 'UninstallAppGroupControl';
const EVENT_ID_APP_REMOVE: string = 'EVENT_ID_APP_LIST_REMOVE';
const SELECT_ALL_ID: string = 'selectAll';
const APP_NOT_FIND: number = -1;
const ITEM_ID_PREFIX: string = '*';
const CHANGE_UNINSTALL_APP_EVENT: string = 'change_chose_app_uninstall';
const PRE_INSTALL_APP_FLAG: string = 'pre-installed';
const CHECKBOX_STYLE: SettingCheckboxStyle = {
  width: 20,
  height: 20
};

class BundleStats {
  public abilityPrevAccessTime: number = 0;
  public name: string = '';
  public appIndex: number = 0;
}

@Builder
function appCloneBadgeBuilder(param: object): void {
  AppCloneBadgeComponent({
    appCloneBadgeParam: param as SettingItemModel,
    iconSize: 48,
    bundleName: ((param as SettingItemModel)?.extra as AppEntry)?.name
  });
}

export class UninstallUnusedAppController implements ComponentControl, AppEntryChangedListener {
  private appList: AppEntryStorage[] = [];
  private dataSource?: OrderedDataSource;
  private compId?: string;
  private selectedApps: AppEntryStorage[] = [];
  private dealRemoveAppCallback = (bundleName: string, appIndex: number) => {
    LogUtil.showInfo(TAG, `dealRemoveAppCallback bundleName:${bundleName} ${appIndex}`);
    this.removeAppData(bundleName, appIndex);
    this.appList = this.appList.filter((app) => {
      if (!AppUtils.isCloneBundle(appIndex)) {
        return app.name !== bundleName;
      }
      return app.name !== bundleName || app.appIndex !== appIndex;
    });
    LogUtil.info(`${TAG} now appListLength is: ${this.appList?.length}`);
    if (!this.appList || this.appList.length <= 0) {
      PageRouter.pop('Setting');
    }
    this.selectedApps = this.selectedApps.filter((app) => {
      if (!AppUtils.isCloneBundle(appIndex)) {
        return app.name !== bundleName;
      }
      return app.name !== bundleName || app.appIndex !== appIndex;
    })
  }
  private dealAppChangeCallback = (data: object) => {
    setTimeout(async () => {
      LogUtil.info(`${TAG} storage app change`)
      await this.getAppData();
    }, 0);
  }

  async init(compParam: CompCtrlParam): Promise<void> {
    if (!compParam || !compParam.compId) {
      LogUtil.error(`${TAG} init fail, compParam is invalid`);
      return;
    }
    this.dataSource = compParam.dataSource as OrderedDataSource;
    this.compId = compParam.compId;
    this.handleEmitter();
    this.getAppData();
  }

  private onBundleResourcesChangedCallback: (isChanged: boolean) => void = async (isChanged: boolean) => {
    LogUtil.info(`${TAG} bundle resources changed: ${isChanged}`);
    if (isChanged) {
      EventBus.getInstance().emit(APP_LOADING_DONE_EVENT, true);
      await AppListLoader.getInstance().refreshAppList();
      LogUtil.info(`${TAG} onBundleResourcesChangedCallback appList length: ${this.appList.length}`);
      EventBus.getInstance().emit(APP_LOADING_DONE_EVENT, false);
    }
  };

  // 获取APP信息并排序
  private async getAppData(apps?: AppEntryStorage[]): Promise<void> {
    if (AppListLoader.getInstance().isRefreshAppList()) {
      LogUtil.warn(`${TAG} appList is isRefreshing`);
      return;
    }
    let appList = (apps && apps.length > 0) ? apps : AppListLoader.getInstance().getAppListCache() as AppEntryStorage[];
    if (!appList || appList.length <= 0) {
      LogUtil.error(`${TAG} appList is empty`);
      PageRouter.pop('Setting');
      return;
    }
    let bundleInfos: bundleManager.BundleInfo[] = await StorageUtil.getUnusedBundleInfoList();
    if (!bundleInfos || bundleInfos.length <= 0) {
      LogUtil.error(`${TAG} bundleInfos is empty`);
      PageRouter.pop('Setting');
      return;
    }
    appList = this.filterApp(appList, bundleInfos);
    await this.getAllBundleStats(appList);
    this.appList = appList;
    this.sortAppList();
    this.reloadDataSource();
  }

  private filterApp(appList: AppEntryStorage[], bundleInfos: Array<bundleManager.BundleInfo>): AppEntryStorage[] {
    let apps: AppEntryStorage[] = [];
    let appMap: Map<string, AppEntryStorage> = new Map();
    appList.forEach((app) => {
      (app as AppEntryStorage).isSelected = false;
      appMap.set(AppUtils.getAppKey(app.name, app.appIndex), app);
    });
    bundleInfos.forEach((bundleInfo) => {
      let app = appMap.get(AppUtils.getAppKey(bundleInfo.name, bundleInfo.appIndex));
      if (app) {
        LogUtil.info(`${TAG} name:${bundleInfo.name}, index:${bundleInfo.appIndex},` +
          `source:${bundleInfo.appInfo?.installSource}, updateTime:${bundleInfo.updateTime}` +
          `, installTime:${bundleInfo.installTime}`);
        app.abilityPrevAccessTime = this.getAppPrevAccessTime(bundleInfo);
        app.unusedDaysDescription = this.getAppUnusedDescription(app.abilityPrevAccessTime);
        apps.push(app);
      }
    });
    LogUtil.info(`${TAG} filtered app length: ${apps.length}`);
    return apps;
  }

  private getAppPrevAccessTime(bundle: bundleManager.BundleInfo): number {
    if (bundle?.appInfo?.installSource === PRE_INSTALL_APP_FLAG && bundle?.installTime === bundle?.updateTime) {
      // 预装应用并且安装时间=更新时间,显示的字符串为未使用的应用,排序时时间戳从小到大排序,需要排在前面
      return 0;
    }
    return bundle.updateTime;
  }

  private getAppUnusedDescription(prevAccessTime: number): ResourceStr {
    if (!prevAccessTime) {
      return $r('app.string.unused_app');
    }
    return this.getPluralStringValueSync($r('app.plural.not_enabled_for_many_days'),
      StorageUtil.getDaysByTimeMillis(prevAccessTime));
  }

  private getPluralStringValueSync(resource: Resource, num: number): string {
    if (!resource) {
      return '';
    }
    try {
      let resourceManager = ResourceUtil.getContext()?.resourceManager;
      return resourceManager?.getPluralStringValueSync(resource.id, num);
    } catch (error) {
      LogUtil.error(`${TAG} getPluralStringValueSync failed, error code: ${error?.code}, message: ${error?.message}.`);
    }
    return '';
  }

  private async getAllBundleStats(appList: AppEntryStorage[]): Promise<void> {
    try {
      LogUtil.info(`${TAG} getBundleStats start`);
      let retArray: BundleStats[] = await this.getBundleStats(appList);
      LogUtil.info(`${TAG} getBundleStats end`);
      let map: HashMap<string, number> = this.getAppTimeStatsHashMap(retArray);
      if (!map || map.isEmpty()) {
        LogUtil.info(`${TAG} no need update appList for map is empty`);
        return;
      }
      appList.forEach((app) => {
        let prevAccessTime: number = map.get(AppUtils.getAppKey(app.name, app.appIndex));
        if (prevAccessTime > 0) {
          app.abilityPrevAccessTime = prevAccessTime;
          app.unusedDaysDescription = this.getPluralStringValueSync($r('app.plural.not_enabled_for_many_days'),
            StorageUtil.getDaysByTimeMillis(prevAccessTime));
        }
      })
    } catch (error) {
      LogUtil.error(`${TAG} getAllBundleStats catch exception, errmsg: ${error?.message}`);
    }
  }

  async getBundleStats(appEntries: AppEntryStorage[]): Promise<BundleStats[]> {
    let promiseList: BundleStats[] = [];
    let record: Record<string, number[]> = this.convertArrayToRecord(appEntries);
    let info: usageStatistics.AppStatsMap | undefined = undefined;
    try {
      info = await usageStatistics.queryLastUseTime(record);
    } catch (err) {
      LogUtil.error(`${TAG} queryLastUseTime error msg:${err?.msg},code:${err?.code}`);
    }
    if (!info) {
      LogUtil.info(`${TAG} getBundleStats fail info is empty`);
      return promiseList;
    }
    for (let app of appEntries) {
      let bundleInfoResults: usageStatistics.BundleStatsInfo[] = info[app.name];
      if (!bundleInfoResults) {
        continue;
      }
      bundleInfoResults.forEach((bundle) => {
        if (bundle?.appIndex !== app.appIndex) {
          return;
        }
        let prevAccessTime: number | undefined = bundle.abilityPrevAccessTime;
        if (!prevAccessTime || prevAccessTime <= 0) {
          return;
        }
        LogUtil.info(`${TAG} queryLastUseTime result name:${bundle.bundleName},
          index:${bundle.appIndex},prevAccessTime${prevAccessTime}`);
        promiseList.push({
          name: app.name,
          appIndex: app.appIndex,
          abilityPrevAccessTime: prevAccessTime,
        });
      });
    }
    return promiseList;
  }

  private convertArrayToRecord(appEntries: AppEntryStorage[]): Record<string, number[]> {
    let record: Record<string, number[]> = {};
    if (!appEntries) {
      return record;
    }
    for (let app of appEntries) {
      let appIndexes: number[] = record[app.name] ?? [];
      appIndexes.push(app.appIndex);
      record[app.name] = appIndexes;
    }
    return record;
  }

  private getAppTimeStatsHashMap(list: BundleStats[]): HashMap<string, number> {
    let hashMap: HashMap<string, number> = new HashMap();
    if (list.length <= 0) {
      LogUtil.error(`${TAG} getAppTimeStatsHashMap error for list is empty`);
      return hashMap;
    }
    list.forEach((item) => {
      hashMap.set(AppUtils.getAppKey(item.name, item.appIndex), item.abilityPrevAccessTime);
    })
    return hashMap;
  }

  onAppUpdate(): void {
    LogUtil.info(`${TAG} onAppUpdate`);
  }

  onAppRemove(bundleName: string, appIndex?: number): void {
    LogUtil.info(`${TAG} onAppRemove`);
  }

  getListenerName(): string {
    return 'UninstallUnusedAppController';
  }

  onAppListChanged(appList: AppEntry[]): void {
    LogUtil.info(`${TAG} onAppListChanged`);
    this.getAppData(appList as AppEntryStorage[]);
  }

  private handleEmitter(): void {
    EventBus.getInstance().on(EVENT_ID_APP_REMOVE, this.dealRemoveAppCallback);
    EventBus.getInstance().on(STORAGE_APP_CHANGE_EVENT, this.dealAppChangeCallback);
    EventBus.getInstance().on(EVENT_ID_BUNDLE_RESOURCES_CHANGED, this.onBundleResourcesChangedCallback);
  }

  private isMainApp(index: number | undefined): boolean {
    return index === 0 || index === undefined;
  }

  private getCloneAppIds(bundleName: string): number[] {
    let appIds: number[] = [];
    if (!this.appList) {
      LogUtil.showInfo(TAG, 'getCloneAppIds appList is null');
      return appIds;
    }
    let cloneBundleApps: AppEntryStorage[] = this.appList.filter((i) => {
      return i.name === bundleName && AppUtils.isCloneBundle(i.appIndex);
    });

    if (CheckEmptyUtils.isEmptyArr(cloneBundleApps)) {
      LogUtil.showInfo(TAG, 'no has clone app');
      return appIds;
    }
    for (let app of cloneBundleApps) {
      appIds.push(app.appIndex);
    }
    return appIds;
  }

  private addSelectAllItem(): void {
    this.dataSource?.splice(0, this.dataSource.length);
    this.dataSource?.pushData({
      id: SELECT_ALL_ID,
      type: ItemType.ITEM_TYPE_STANDARD,
      title: { content: $r('app.string.select_all') },
      result: {
        type: ItemResultType.RESULT_TYPE_CHECKBOX,
        result: {
          selected: this.appList.length !== 0 && this.appList?.length === this.selectedApps?.length,
          style: CHECKBOX_STYLE,
          onSelected: (isSelected: boolean, item: SettingItemModel) => {
            if (isSelected && this.selectedApps.length === this.appList.length) {
              return;
            }
            if (!isSelected && this.selectedApps.length === 0) {
              return;
            }
            item.id = this.refreshItemId(item.id);
            this.revertDataSelectResult(isSelected);
            this.handleAllSelect(isSelected);
          }
        }
      },
    });
  }

  /**
   * 全选和取消全选后,更新已选择app列表,并发送列表更新事件
   */
  private handleAllSelect(select: boolean): void {
    if (select) {
      this.selectedApps.splice(0, this.selectedApps.length);
      this.appList.forEach((app) => {
        this.selectedApps.push(app);
      })
    } else {
      this.selectedApps.splice(0, this.selectedApps.length);
    }
    EventBus.getInstance().emit(CHANGE_UNINSTALL_APP_EVENT, this.selectedApps);
  }

  /**
   * 处理全选和取消全选的逻辑,重置数据的选择结果,并通知UI刷新
   */
  private revertDataSelectResult(select: boolean): void {
    let lastDataSource: OrderedDataSource | undefined = this.dataSource;
    this.dataSource = undefined;
    lastDataSource?.forEach((data) => {
      (data.result?.result as SettingCheckboxModel).selected = select;
      data.id = this.refreshItemId(data.id);
    })
    this.dataSource = lastDataSource;
    this.dataSource?.notifyDataReload();
  }

  private getIconStyle(): SettingIconStyle {
    // 接入了HDS之后,图标不需要主动做描边处理
    let style: SettingIconStyle = {
      border: { width: '0px', color: '#00000000', radius: 0 },
      borderRadius: 0,
      width: 48,
      height: 48,
      mirrored: false,
      draggable: false
    };
    return style;
  }

  private sortAppList(): void {
    this.appList.sort((first, second) => first.abilityPrevAccessTime - second.abilityPrevAccessTime);
  }

  /**
   * item的选择状态发生变化后,刷新id以更新UI,只需要与上一次的id不一样即可
   */
  private refreshItemId(beforeId: string): string {
    if (CheckEmptyUtils.checkStrIsEmpty(beforeId) || !beforeId.startsWith(ITEM_ID_PREFIX)) {
      return ITEM_ID_PREFIX + beforeId;
    }
    return beforeId.substring(ITEM_ID_PREFIX.length);
  }

  private findAppIndex(appName: string, appIndex: number): number {
    for (let i = 0; i < this.appList.length; i++) {
      let curApp = this.appList[i];
      if (AppUtils.getAppKey(curApp.name, curApp.appIndex) === AppUtils.getAppKey(appName, appIndex)) {
        return i;
      }
    }
    return APP_NOT_FIND;
  }

  private reloadDataSource(): void {
    this.addSelectAllItem();
    let items: ComparableSettingItemModel[] = [];
    this.appList.forEach((item) => {
      let obj: ComparableSettingItemModel = {
        id: AppUtils.getAppKey(item.name, item.appIndex),
        type: ItemType.ITEM_TYPE_STANDARD,
        icon: {
          icon: AppUtils.isCloneBundle(item.appIndex) ?
            AppListLoader.getInstance().getAppEntry(item.name)?.icon as ResourceStr : item.icon as ResourceStr,
          iconType: SettingIconType.ICON_TYPE_APPICON,
          style: this.getIconStyle(),
          builder: AppUtils.isSupportShowIconBadge(item) ? wrapBuilder(appCloneBadgeBuilder) : undefined
        },
        title: { content: item?.label ?? '' },
        result: {
          type: ItemResultType.RESULT_TYPE_CHECKBOX,
          result: {
            selected: item.isSelected,
            style: CHECKBOX_STYLE,
            onSelected: (isSelected: boolean, model: SettingItemModel) => {
              model.id = this.refreshItemId(model.id);
              let index = this.findAppIndex(item.name, item.appIndex);
              this.dataSource?.notifyDataChange(index);
              isSelected ? this.handleSingleItemSelected(item) : this.handleSingleItemDeselected(item);
              this.handleSingleItemSelectChange(item, model, isSelected);
            }
          }
        },
        desc: { content: item.unusedDaysDescription },
        extra: item
      }
      items.push(obj);
    })
    if (this.dataSource) {
      this.dataSource?.pushDataArray(items);
      this.dataSource.notifyDataReload();
    }
  }

  /**
   * 单个item选择状态变化后更新id以更新UI
   */
  private handleSingleItemSelectChange(item: AppEntryStorage, model: SettingItemModel, isSelected: boolean): void {
    if (!item || !model) {
      return;
    }
    (model?.result?.result as SettingCheckboxModel).selected = isSelected;
    model.id = this.refreshItemId(model.id);
    let index = this.findAppIndex(item.name, item.appIndex);
    if (index !== APP_NOT_FIND) {
      this.dataSource?.notifyDataChange(index);
    }
  }

  /**
   * 处理单个item选择后的事件
   */
  private handleSingleItemSelected(item: AppEntryStorage): void {
    if (this.selectedApps.includes(item)) {
      return;
    }
    this.selectedApps.push(item);
    EventBus.getInstance().emit(CHANGE_UNINSTALL_APP_EVENT, this.selectedApps);
    if (this.selectedApps.length !== this.appList.length) {
      return;
    }
    if (this.dataSource) {
      this.dataSource[0].id = this.refreshItemId(this.dataSource[0].id);
      (this.dataSource[0].result?.result as SettingCheckboxModel).selected = true;
      this.dataSource.notifyDataChange(0);
    }
  }

  /**
   * 处理单个item取消选择后的事件
   */
  private handleSingleItemDeselected(item: AppEntryStorage): void {
    if (!this.selectedApps.includes(item)) {
      return;
    }
    for (let i = 0; i < this.selectedApps.length; i++) {
      if (this.selectedApps[i] === item) {
        this.selectedApps.splice(i, 1);
        break;
      }
    }
    EventBus.getInstance().emit(CHANGE_UNINSTALL_APP_EVENT, this.selectedApps);
    if (this.selectedApps.length !== this.appList.length - 1) {
      return;
    }
    if (this.dataSource) {
      this.dataSource[0].id = this.refreshItemId(this.dataSource[0].id);
      (this.dataSource[0].result?.result as SettingCheckboxModel).selected = false;
      this.dataSource.notifyDataChange(0);
    }
  }

  private removeAppData(name: string, appIndex: number): void {
    let lastData = this.dataSource;
    let index: number = this.findAppIndex(name, appIndex);
    if (index >= 0 && lastData && index < lastData.length) {
      LogUtil.info(TAG + ', uninstall app with index:' + index);
      lastData.removeDataByIndex(++index); // dataSource第一个元素是全选,因此序号需要加1
    }
    if (!this.isMainApp(appIndex)) {
      return;
    }
    let cloneApps: number[] = this.getCloneAppIds(name);
    for (let id of cloneApps) {
      index = this.findAppIndex(name, id);
      lastData?.removeDataByIndex(++index);
    }
  }

  protected registerDataChange(): void {
    AppListLoader.getInstance().unRegisterAppChangedListener(this);
    AppListLoader.getInstance().registerAppChangedListener(this);
  }

  destroy(): void {
    EventBus.getInstance().detach(EVENT_ID_APP_REMOVE, this.dealRemoveAppCallback);
    EventBus.getInstance().detach(STORAGE_APP_CHANGE_EVENT, this.dealAppChangeCallback);
    EventBus.getInstance().detach(EVENT_ID_BUNDLE_RESOURCES_CHANGED, this.onBundleResourcesChangedCallback);
    LogUtil.showInfo(TAG, 'onDestroy eventbus');
  }
}