import { ElectronIPCEventHandler, ElectronIPCServer } from '@lobechat/electron-server-ipc';
import { Session, app, ipcMain, protocol } from 'electron';
import { macOS, windows } from 'electron-is';
import os from 'node:os';
import { join } from 'node:path';

import { name } from '@/../../package.json';
import { buildDir, nextStandaloneDir } from '@/const/dir';
import { isDev } from '@/const/env';
import { IControlModule } from '@/controllers';
import { IServiceModule } from '@/services';
import { IpcClientEventSender } from '@/types/ipcClientEvent';
import { createLogger } from '@/utils/logger';
import { CustomRequestHandler, createHandler } from '@/utils/next-electron-rsc';

import BrowserManager from './BrowserManager';
import { I18nManager } from './I18nManager';
import { IoCContainer } from './IoCContainer';
import MenuManager from './MenuManager';
import { ShortcutManager } from './ShortcutManager';
import { StoreManager } from './StoreManager';
import TrayManager from './TrayManager';
import { UpdaterManager } from './UpdaterManager';

const logger = createLogger('core:App');

export type IPCEventMap = Map<string, { controller: any; methodName: string }>;
export type ShortcutMethodMap = Map<string, () => Promise<void>>;

type Class<T> = new (...args: any[]) => T;

const importAll = (r: any) => Object.values(r).map((v: any) => v.default);

export class App {
  nextServerUrl = 'http://localhost:3015';

  browserManager: BrowserManager;
  menuManager: MenuManager;
  i18n: I18nManager;
  storeManager: StoreManager;
  updaterManager: UpdaterManager;
  shortcutManager: ShortcutManager;
  trayManager: TrayManager;
  chromeFlags: string[] = ['OverlayScrollbar', 'FluentOverlayScrollbar', 'FluentScrollbar'];

  /**
   * whether app is in quiting
   */
  isQuiting: boolean = false;

  get appStoragePath() {
    const storagePath = this.storeManager.get('storagePath');

    if (!storagePath) {
      throw new Error('Storage path not found in store');
    }

    return storagePath;
  }

  constructor() {
    logger.info('----------------------------------------------');
    // Log system information
    logger.info(`  OS: ${os.platform()} (${os.arch()})`);
    logger.info(` CPU: ${os.cpus().length} cores`);
    logger.info(` RAM: ${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB`);
    logger.info(`PATH: ${app.getAppPath()}`);
    logger.info(` lng: ${app.getLocale()}`);
    logger.info('----------------------------------------------');
    logger.info('Starting LobeHub...');

    logger.debug('Initializing App');
    // Initialize store manager
    this.storeManager = new StoreManager(this);

    // load controllers
    const controllers: IControlModule[] = importAll(
      (import.meta as any).glob('@/controllers/*Ctr.ts', { eager: true }),
    );

    logger.debug(`Loading ${controllers.length} controllers`);
    controllers.forEach((controller) => this.addController(controller));

    // load services
    const services: IServiceModule[] = importAll(
      (import.meta as any).glob('@/services/*Srv.ts', { eager: true }),
    );

    logger.debug(`Loading ${services.length} services`);
    services.forEach((service) => this.addService(service));

    this.initializeIPCEvents();

    this.i18n = new I18nManager(this);
    this.browserManager = new BrowserManager(this);
    this.menuManager = new MenuManager(this);
    this.updaterManager = new UpdaterManager(this);
    this.shortcutManager = new ShortcutManager(this);
    this.trayManager = new TrayManager(this);

    // register the schema to interceptor url
    // it should register before app ready
    this.registerNextHandler();

    // 统一处理 before-quit 事件
    app.on('before-quit', this.handleBeforeQuit);

    logger.info('App initialization completed');
  }

  bootstrap = async () => {
    logger.info('Bootstrapping application');
    // make single instance
    const isSingle = app.requestSingleInstanceLock();
    if (!isSingle) {
      logger.info('Another instance is already running, exiting');
      app.exit(0);
    }

    this.initDevBranding();

    //  ==============
    await this.ipcServer.start();
    logger.debug('IPC server started');

    // Initialize app
    await this.makeAppReady();

    // Initialize i18n. Note: app.getLocale() must be called after app.whenReady() to get the correct value
    await this.i18n.init();
    this.menuManager.initialize();

    // Initialize global shortcuts: globalShortcut must be called after app.whenReady()
    this.shortcutManager.initialize();

    this.browserManager.initializeBrowsers();

    // Initialize tray manager
    if (process.platform === 'win32') {
      this.trayManager.initializeTrays();
    }

    // Initialize updater manager
    await this.updaterManager.initialize();

    // Set global application exit state
    this.isQuiting = false;

    app.on('window-all-closed', () => {
      if (windows()) {
        logger.info('All windows closed, quitting application (Windows)');
        app.quit();
      }
    });

    app.on('activate', this.onActivate);
    logger.info('Application bootstrap completed');
  };

  getService<T>(serviceClass: Class<T>): T {
    return this.services.get(serviceClass);
  }

  getController<T>(controllerClass: Class<T>): T {
    return this.controllers.get(controllerClass);
  }

  private onActivate = () => {
    logger.debug('Application activated');
    this.browserManager.showMainWindow();
  };

  /**
   * Call beforeAppReady method on all controllers before the application is ready
   */
  private makeAppReady = async () => {
    logger.debug('Preparing application ready state');
    this.controllers.forEach((controller) => {
      if (typeof controller.beforeAppReady === 'function') {
        try {
          controller.beforeAppReady();
        } catch (error) {
          logger.error(`Error in controller.beforeAppReady:`, error);
          console.error(`[App] Error in controller.beforeAppReady:`, error);
        }
      }
    });

    // refs: https://github.com/lobehub/lobe-chat/pull/7883
    // https://github.com/electron/electron/issues/46538#issuecomment-2808806722
    app.commandLine.appendSwitch('gtk-version', '3');

    app.commandLine.appendSwitch('enable-features', this.chromeFlags.join(','));

    logger.debug('Waiting for app to be ready');
    await app.whenReady();
    logger.debug('Application ready');

    this.controllers.forEach((controller) => {
      if (typeof controller.afterAppReady === 'function') {
        try {
          controller.afterAppReady();
        } catch (error) {
          logger.error(`Error in controller.afterAppReady:`, error);
          console.error(`[App] Error in controller.beforeAppReady:`, error);
        }
      }
    });
    logger.info('Application ready state completed');
  };

  // ============= helper ============= //

  /**
   * all controllers in app
   */
  private controllers = new Map<Class<any>, any>();
  /**
   * all services in app
   */
  private services = new Map<Class<any>, any>();

  private ipcServer: ElectronIPCServer;
  /**
   * events dispatched from webview layer
   */
  private ipcClientEventMap: IPCEventMap = new Map();
  private ipcServerEventMap: IPCEventMap = new Map();
  shortcutMethodMap: ShortcutMethodMap = new Map();

  /**
   * use in next router interceptor in prod browser render
   */
  nextInterceptor: (params: { session: Session }) => () => void;

  /**
   * Collection of unregister functions for custom request handlers
   */
  private customHandlerUnregisterFns: Array<() => void> = [];

  /**
   * Function to register custom request handler
   */
  private registerCustomHandlerFn?: (handler: CustomRequestHandler) => () => void;

  /**
   * Register custom request handler
   * @param handler Custom request handler function
   * @returns Function to unregister the handler
   */
  registerRequestHandler = (handler: CustomRequestHandler): (() => void) => {
    if (!this.registerCustomHandlerFn) {
      logger.warn('Custom request handler registration is not available');
      return () => {};
    }

    logger.debug('Registering custom request handler');
    const unregisterFn = this.registerCustomHandlerFn(handler);
    this.customHandlerUnregisterFns.push(unregisterFn);

    return () => {
      unregisterFn();
      const index = this.customHandlerUnregisterFns.indexOf(unregisterFn);
      if (index !== -1) {
        this.customHandlerUnregisterFns.splice(index, 1);
      }
    };
  };

  /**
   * Unregister all custom request handlers
   */
  unregisterAllRequestHandlers = () => {
    this.customHandlerUnregisterFns.forEach((unregister) => unregister());
    this.customHandlerUnregisterFns = [];
  };

  private addController = (ControllerClass: IControlModule) => {
    const controller = new ControllerClass(this);
    this.controllers.set(ControllerClass, controller);

    IoCContainer.controllers.get(ControllerClass)?.forEach((event) => {
      if (event.mode === 'client') {
        // Store all objects from event decorator in ipcClientEventMap
        this.ipcClientEventMap.set(event.name, {
          controller,
          methodName: event.methodName,
        });
      }

      if (event.mode === 'server') {
        // Store all objects from event decorator in ipcServerEventMap
        this.ipcServerEventMap.set(event.name, {
          controller,
          methodName: event.methodName,
        });
      }
    });

    IoCContainer.shortcuts.get(ControllerClass)?.forEach((shortcut) => {
      this.shortcutMethodMap.set(shortcut.name, async () => {
        controller[shortcut.methodName]();
      });
    });
  };

  private addService = (ServiceClass: IServiceModule) => {
    const service = new ServiceClass(this);
    this.services.set(ServiceClass, service);
  };

  private initDevBranding = () => {
    if (!isDev) return;

    logger.debug('Setting up dev branding');
    app.setName('lobehub-desktop-dev');
    if (macOS()) {
      app.dock!.setIcon(join(buildDir, 'icon-dev.png'));
    }
  };

  private registerNextHandler() {
    logger.debug('Registering Next.js handler');
    const handler = createHandler({
      debug: true,
      localhostUrl: this.nextServerUrl,
      protocol,
      standaloneDir: nextStandaloneDir,
    });

    // Log output based on development or production mode
    if (isDev) {
      logger.info(
        `Development mode: Custom request handler enabled, but Next.js interception disabled`,
      );
    } else {
      logger.info(
        `Production mode: ${this.nextServerUrl} will be intercepted to ${nextStandaloneDir}`,
      );
    }

    this.nextInterceptor = handler.createInterceptor;

    // Save custom handler registration function
    if (handler.registerCustomHandler) {
      this.registerCustomHandlerFn = handler.registerCustomHandler;
      logger.debug('Custom request handler registration is available');
    } else {
      logger.warn('Custom request handler registration is not available');
    }
  }

  private initializeIPCEvents() {
    logger.debug('Initializing IPC events');
    // Register batch controller client events for render side consumption
    this.ipcClientEventMap.forEach((eventInfo, key) => {
      const { controller, methodName } = eventInfo;

      ipcMain.handle(key, async (e, data) => {
        // 从 WebContents 获取对应的 BrowserWindow id
        const senderIdentifier = this.browserManager.getIdentifierByWebContents(e.sender);
        try {
          return await controller[methodName](data, {
            identifier: senderIdentifier,
          } as IpcClientEventSender);
        } catch (error) {
          logger.error(`Error handling IPC event ${key}:`, error);
          return { error: error.message };
        }
      });
    });

    // Batch register server events from controllers for next server consumption
    const ipcServerEvents = {} as ElectronIPCEventHandler;

    this.ipcServerEventMap.forEach((eventInfo, key) => {
      const { controller, methodName } = eventInfo;

      ipcServerEvents[key] = async (payload) => {
        try {
          return await controller[methodName](payload);
        } catch (error) {
          return { error: error.message };
        }
      };
    });

    this.ipcServer = new ElectronIPCServer(name, ipcServerEvents);
  }

  // 新增 before-quit 处理函数
  private handleBeforeQuit = () => {
    logger.info('Application is preparing to quit');
    this.isQuiting = true;

    // 销毁托盘
    if (process.platform === 'win32') {
      this.trayManager.destroyAll();
    }

    // 执行清理操作
    this.unregisterAllRequestHandlers();
  };
}
