import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireAdminIdentity } from '../helpers/guards.ts';
import { logger } from '../../logging.ts';
import { getErrorMessage } from '../../utils/error.ts';
import { isValidHostname, normalizeHostname } from '../../utils/domain.ts';

export class SettingsHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();

  constructor(private opsServerRef: OpsServer) {
    this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
    this.registerHandlers();
  }

  private async getSettingsObject(): Promise<interfaces.data.ISettings> {
    const db = this.opsServerRef.oneboxRef.database;
    const cloudflareToken = await db.getSecretSetting('cloudflareToken');
    const dcrouterGatewayApiToken = await db.getSecretSetting('dcrouterGatewayApiToken');
    const settingsMap = db.getAllSettings();
    const managedDcRouter = this.opsServerRef.oneboxRef.managedDcRouter;

    return {
      cloudflareToken: cloudflareToken || '',
      cloudflareZoneId: settingsMap['cloudflareZoneId'] || '',
      adminUiDomain: settingsMap['adminUiDomain'] || '',
      dcrouterMode: managedDcRouter.getMode(),
      dcrouterManagedImage: managedDcRouter.getImage(),
      dcrouterManagedOpsPort: managedDcRouter.getOpsPort(),
      dcrouterManagedHttpPort: managedDcRouter.getHttpPort(),
      dcrouterManagedHttpsPort: managedDcRouter.getHttpsPort(),
      dcrouterManagedDataDir: managedDcRouter.getDataDir(),
      dcrouterGatewayUrl: settingsMap['dcrouterGatewayUrl'] || '',
      dcrouterGatewayApiToken: dcrouterGatewayApiToken || '',
      dcrouterGatewayClientId: settingsMap['dcrouterGatewayClientId'] || settingsMap['dcrouterWorkHosterId'] || '',
      dcrouterWorkHosterId: settingsMap['dcrouterWorkHosterId'] || settingsMap['dcrouterGatewayClientId'] || '',
      dcrouterTargetHost: settingsMap['dcrouterTargetHost'] || '',
      dcrouterTargetPort: parseInt(settingsMap['dcrouterTargetPort'] || '0', 10),
      autoRenewCerts: settingsMap['autoRenewCerts'] === 'true',
      renewalThreshold: parseInt(settingsMap['renewalThreshold'] || '30', 10),
      acmeEmail: settingsMap['acmeEmail'] || '',
      httpPort: parseInt(settingsMap['httpPort'] || '80', 10),
      httpsPort: parseInt(settingsMap['httpsPort'] || '443', 10),
      forceHttps: settingsMap['forceHttps'] === 'true',
    };
  }

  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSettings>(
        'getSettings',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const settings = await this.getSettingsObject();
          return { settings };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSettings>(
        'updateSettings',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const db = this.opsServerRef.oneboxRef.database;
          const updates = dataArg.settings;

          const normalizedUpdates = this.normalizeUpdates(updates);

          // Store each setting as key-value pair
          for (const [key, value] of Object.entries(normalizedUpdates)) {
            if (value !== undefined) {
              if (db.isSecretSettingKey(key)) {
                await db.setSecretSetting(key, String(value));
              } else {
                db.setSetting(key, String(value));
              }
            }
          }

          if (this.hasRouteSyncSetting(normalizedUpdates)) {
            this.refreshGatewayRoutes(normalizedUpdates).catch((error) => {
              logger.warn(`dcrouter gateway settings refresh failed: ${getErrorMessage(error)}`);
            });
          }

          const settings = await this.getSettingsObject();
          return { settings };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetBackupPassword>(
        'setBackupPassword',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.opsServerRef.oneboxRef.database.setSecretSetting('backupPassword', dataArg.password);
          return { ok: true };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupPasswordStatus>(
        'getBackupPasswordStatus',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const isConfigured = await this.opsServerRef.oneboxRef.database.hasSecretSetting('backupPassword');
          return { status: { isConfigured } };
        },
      ),
    );
  }

  private normalizeUpdates(
    settings: Partial<interfaces.data.ISettings>,
  ): Partial<interfaces.data.ISettings> {
    const normalizedUpdates = { ...settings };
    if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'adminUiDomain')) {
      const normalizedDomain = normalizeHostname(String(normalizedUpdates.adminUiDomain || ''));
      if (!isValidHostname(normalizedDomain)) {
        throw new plugins.typedrequest.TypedResponseError('Invalid Admin UI domain');
      }
      normalizedUpdates.adminUiDomain = normalizedDomain;
    }
    return normalizedUpdates;
  }

  private hasRouteSyncSetting(settings: Partial<interfaces.data.ISettings>): boolean {
    return [
      'adminUiDomain',
      'dcrouterMode',
      'dcrouterManagedImage',
      'dcrouterManagedOpsPort',
      'dcrouterManagedHttpPort',
      'dcrouterManagedHttpsPort',
      'dcrouterManagedDataDir',
      'dcrouterGatewayUrl',
      'dcrouterGatewayApiToken',
      'dcrouterGatewayClientId',
      'dcrouterWorkHosterId',
      'dcrouterTargetHost',
      'dcrouterTargetPort',
    ].some((key) => Object.prototype.hasOwnProperty.call(settings, key));
  }

  private hasManagedDcRouterRuntimeSetting(settings: Partial<interfaces.data.ISettings>): boolean {
    return [
      'dcrouterMode',
      'dcrouterManagedImage',
      'dcrouterManagedOpsPort',
      'dcrouterManagedHttpPort',
      'dcrouterManagedHttpsPort',
      'dcrouterManagedDataDir',
    ].some((key) => Object.prototype.hasOwnProperty.call(settings, key));
  }

  private async refreshGatewayRoutes(settings: Partial<interfaces.data.ISettings>): Promise<void> {
    const onebox = this.opsServerRef.oneboxRef;
    if (this.hasManagedDcRouterRuntimeSetting(settings)) {
      if (onebox.managedDcRouter.getMode() === 'managed') {
        await onebox.managedDcRouter.restart();
      } else {
        await onebox.managedDcRouter.stop();
      }
    }

    await onebox.reverseProxy.reloadRoutes();
    await onebox.externalGateway.syncDomains();
    await onebox.externalGateway.syncServiceRoutes();
  }
}
