import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
import { requireOpsAuth } from '../helpers/auth.js';

type TAuthContext = {
  userId: string;
  isAdmin: boolean;
  token?: interfaces.data.IStoredApiToken;
};

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

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

  private async requireAuth(
    request: { identity?: interfaces.data.IIdentity; apiToken?: string },
    requiredScope?: interfaces.data.TApiTokenScope,
  ): Promise<TAuthContext> {
    const auth = await requireOpsAuth(this.opsServerRef, request, {
      scope: requiredScope,
      requireAdminIdentity: requiredScope?.endsWith(':write'),
    });
    return { userId: auth.userId, isAdmin: auth.isAdmin, token: auth.token };
  }

  private async requireAdmin(
    request: { identity?: interfaces.data.IIdentity; apiToken?: string },
    scope: interfaces.data.TApiTokenScope = 'gateway-clients:write',
  ): Promise<string> {
    const auth = await requireOpsAuth(this.opsServerRef, request, {
      scope,
      requireAdminIdentity: true,
      requireAdminToken: true,
    });
    return auth.userId;
  }

  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.gateway.IReq_GetGatewayCapabilities>(
        'getGatewayCapabilities',
        async (dataArg) => {
          await this.requireAuth(dataArg, 'gateway-clients:read');
          return { capabilities: this.getGatewayCapabilities() };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.gateway.IReq_GetGatewayClientContext>(
        'getGatewayClientContext',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg, 'gateway-clients:read');
          return {
            context: this.getGatewayClientContext(auth),
            capabilities: this.getGatewayCapabilities(),
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListGatewayClients>(
        'listGatewayClients',
        async (dataArg) => {
          await this.requireAdmin(dataArg, 'gateway-clients:read');
          return { gatewayClients: await this.listManagedGatewayClients() };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateGatewayClient>(
        'createGatewayClient',
        async (dataArg) => {
          const userId = await this.requireAdmin(dataArg);
          const manager = this.opsServerRef.dcRouterRef.gatewayClientManager;
          if (!manager) return { success: false, message: 'Gateway client management not initialized' };
          try {
            const gatewayClient = await manager.createClient({
              id: dataArg.id,
              type: dataArg.type,
              name: dataArg.name,
              description: dataArg.description,
              hostnamePatterns: dataArg.hostnamePatterns,
              allowedRouteTargets: dataArg.allowedRouteTargets,
              capabilities: dataArg.capabilities,
              createdBy: userId,
            });
            return { success: true, gatewayClient };
          } catch (error) {
            return { success: false, message: (error as Error).message };
          }
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateGatewayClient>(
        'updateGatewayClient',
        async (dataArg) => {
          await this.requireAdmin(dataArg);
          const manager = this.opsServerRef.dcRouterRef.gatewayClientManager;
          if (!manager) return { success: false, message: 'Gateway client management not initialized' };
          const gatewayClient = await manager.updateClient(dataArg.id, {
            name: dataArg.name,
            description: dataArg.description,
            hostnamePatterns: dataArg.hostnamePatterns,
            allowedRouteTargets: dataArg.allowedRouteTargets,
            capabilities: dataArg.capabilities,
            enabled: dataArg.enabled,
          });
          return gatewayClient
            ? { success: true, gatewayClient }
            : { success: false, message: 'Gateway client not found' };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteGatewayClient>(
        'deleteGatewayClient',
        async (dataArg) => {
          await this.requireAdmin(dataArg);
          const manager = this.opsServerRef.dcRouterRef.gatewayClientManager;
          if (!manager) return { success: false, message: 'Gateway client management not initialized' };
          const success = await manager.deleteClient(dataArg.id);
          return { success, message: success ? undefined : 'Gateway client not found' };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateGatewayClientToken>(
        'createGatewayClientToken',
        async (dataArg) => {
          const userId = await this.requireAdmin(dataArg, 'tokens:manage');
          const gatewayClient = await this.opsServerRef.dcRouterRef.gatewayClientManager?.getClient(dataArg.gatewayClientId);
          const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
          if (!gatewayClient || !gatewayClient.enabled) {
            return { success: false, message: 'Gateway client not found or disabled' };
          }
          if (!tokenManager) {
            return { success: false, message: 'Token management not initialized' };
          }
          const result = await tokenManager.createToken(
            dataArg.name?.trim() || `${gatewayClient.name} Token`,
            ['gateway-clients:read', 'gateway-clients:write'],
            dataArg.expiresInDays ?? null,
            userId,
            {
              role: 'gatewayClient',
              scopes: ['gateway-clients:read', 'gateway-clients:write'],
              gatewayClient: { type: gatewayClient.type, id: gatewayClient.id },
              hostnamePatterns: gatewayClient.hostnamePatterns,
              allowedRouteTargets: gatewayClient.allowedRouteTargets,
              capabilities: gatewayClient.capabilities,
            },
          );
          return { success: true, tokenId: result.id, tokenValue: result.rawToken };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.gateway.IReq_GetGatewayClientDomains>(
        'getGatewayClientDomains',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg, 'gateway-clients:read');
          this.assertCapability(auth, 'readDomains');
          return { domains: await this.listGatewayClientDomains(auth, dataArg.gatewayClientId) };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.gateway.IReq_GetGatewayClientDnsRecords>(
        'getGatewayClientDnsRecords',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg, 'gateway-clients:read');
          this.assertCapability(auth, 'readDnsRecords');
          return { records: await this.listGatewayClientDnsRecords(auth, dataArg.gatewayClientId) };
        },
      ),
    );


    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.gateway.IReq_SyncGatewayClientRoute>(
        'syncGatewayClientRoute',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg, 'gateway-clients:write');
          this.assertCapability(auth, 'syncRoutes');
          return await this.syncGatewayClientRoute(auth, dataArg.ownership, dataArg.route, dataArg.enabled, dataArg.delete, dataArg.sourceProfileRef);
        },
      ),
    );




    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_ListMailAddressBindings>(
        'listMailAddressBindings',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:read');
          const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
          if (!manager) return { bindings: [] };
          return {
            bindings: await manager.listMailAddressBindings({
              owner: this.resolveMailOwnerFilter(auth, dataArg.owner),
              domain: dataArg.domain,
              address: dataArg.address,
            }),
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_SyncMailAddressBinding>(
        'syncMailAddressBinding',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:write');
          this.assertCapability(auth, 'syncRoutes');
          const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
          if (!manager) {
            return { success: false, message: 'WorkApp mail manager not initialized' };
          }
          try {
            const binding = {
              ...dataArg.binding,
              owner: this.resolveMailOwner(auth, dataArg.binding.owner),
            };
            this.assertMailForwardTargetAllowed(auth, binding.inboundTarget);
            return await manager.syncMailAddressBinding(binding, auth.userId);
          } catch (error) {
            return { success: false, message: (error as Error).message };
          }
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_DeleteMailAddressBinding>(
        'deleteMailAddressBinding',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:write');
          this.assertCapability(auth, 'syncRoutes');
          const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
          if (!manager) {
            return { success: false, message: 'WorkApp mail manager not initialized' };
          }
          if (auth.token?.policy?.role === 'gatewayClient') {
            const bindings = await manager.listMailAddressBindings({
              owner: this.resolveMailOwnerFilter(auth),
            });
            const binding = bindings.find((candidate) => candidate.id === dataArg.id);
            if (!binding) return { success: true };
            return await manager.deleteMailAddressBinding(binding.id, auth.userId);
          }
          return await manager.deleteMailAddressBinding(dataArg.id, auth.userId);
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_ListWorkAppMailBindings>(
        'listWorkAppMailBindings',
        async (dataArg) => {
          const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:read');
          const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
          if (!manager) return { bindings: [] };
          return { bindings: await manager.listWorkAppMailBindings(this.resolveMailOwnerFilter(auth, dataArg.owner)) };
        },
      ),
    );
  }

  private getGatewayCapabilities(): plugins.servezoneInterfaces.data.IGatewayCapabilities {
    const dcRouter = this.opsServerRef.dcRouterRef;
    return {
      routes: {
        read: Boolean(dcRouter.routeConfigManager),
        write: Boolean(dcRouter.routeConfigManager),
        idempotentSync: Boolean(dcRouter.routeConfigManager),
      },
      domains: {
        read: Boolean(dcRouter.dnsManager),
        write: Boolean(dcRouter.dnsManager),
      },
      certificates: {
        read: Boolean(dcRouter.smartProxy),
        export: Boolean(dcRouter.smartProxy),
        forceRenew: Boolean(dcRouter.smartProxy),
      },
      email: {
        domains: Boolean(dcRouter.emailDomainManager),
        inbound: Boolean(dcRouter.emailServer),
        outbound: Boolean(dcRouter.emailServer),
      },
      remoteIngress: {
        enabled: Boolean(dcRouter.remoteIngressManager?.getHubSettings().enabled),
      },
      dns: {
        authoritative: Boolean(dcRouter.options.dnsScopes?.length),
        providerManaged: Boolean(dcRouter.dnsManager),
      },
      http3: {
        enabled: dcRouter.options.http3?.enabled !== false,
      },
    };
  }

  private getGatewayClientContext(auth: TAuthContext): plugins.servezoneInterfaces.data.IGatewayClientContext {
    const policy = auth.token?.policy;
    const role = auth.isAdmin ? 'admin' : policy?.role || 'operator';
    return {
      role,
      scopes: auth.token?.scopes || ['*'],
      gatewayClient: policy?.gatewayClient,
      hostnamePatterns: policy?.hostnamePatterns || [],
      allowedRouteTargets: policy?.allowedRouteTargets || [],
      capabilities: policy?.capabilities || {},
    };
  }

  private async listManagedGatewayClients(): Promise<interfaces.data.IGatewayClient[]> {
    const manager = this.opsServerRef.dcRouterRef.gatewayClientManager;
    if (!manager) return [];
    const clients = await manager.listClients();
    const tokens = this.opsServerRef.dcRouterRef.apiTokenManager?.listTokens() || [];
    return clients.map((client) => ({
      ...client,
      tokenCount: tokens.filter((token) => token.policy?.gatewayClient?.id === client.id).length,
    }));
  }


  private assertCapability(
    auth: TAuthContext,
    capability: keyof NonNullable<interfaces.data.IApiTokenPolicy['capabilities']>,
  ): void {
    if (auth.isAdmin) return;
    const policy = auth.token?.policy;
    if (!policy || policy.role !== 'gatewayClient') return;
    if (policy.capabilities?.[capability] === true) return;
    throw new plugins.typedrequest.TypedResponseError(`token capability missing: ${String(capability)}`);
  }

  private resolveGatewayClientId(auth: TAuthContext, requestedId?: string): string | undefined {
    const policyClient = auth.token?.policy?.gatewayClient;
    if (!policyClient) return requestedId;
    if (requestedId && requestedId !== policyClient.id) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token cannot access another gateway client');
    }
    return policyClient.id;
  }

  private resolveGatewayClientOwnership(
    auth: TAuthContext,
    ownership: plugins.servezoneInterfaces.data.IGatewayClientOwnership,
  ): Required<plugins.servezoneInterfaces.data.IGatewayClientOwnership> {
    const policy = auth.token?.policy;
    if (policy?.role === 'gatewayClient') {
      if (!policy.gatewayClient) {
        throw new plugins.typedrequest.TypedResponseError('gateway client token is missing gatewayClient binding');
      }
      if (ownership.gatewayClientType && ownership.gatewayClientType !== policy.gatewayClient.type) {
        throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
      }
      if (ownership.gatewayClientId && ownership.gatewayClientId !== policy.gatewayClient.id) {
        throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
      }
      return {
        gatewayClientType: policy.gatewayClient.type,
        gatewayClientId: policy.gatewayClient.id,
        appId: ownership.appId,
        hostname: ownership.hostname,
      };
    }

    if (!ownership.gatewayClientType || !ownership.gatewayClientId) {
      throw new plugins.typedrequest.TypedResponseError('gateway client ownership is missing type or id');
    }
    return ownership as Required<plugins.servezoneInterfaces.data.IGatewayClientOwnership>;
  }

  private resolveMailOwnerFilter(
    auth: TAuthContext,
    owner?: Partial<plugins.servezoneInterfaces.data.IMailResourceOwner>,
  ): Partial<plugins.servezoneInterfaces.data.IMailResourceOwner> | undefined {
    const policy = auth.token?.policy;
    if (policy?.role !== 'gatewayClient') return owner;
    if (!policy.gatewayClient) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token is missing gatewayClient binding');
    }
    if (owner?.gatewayClientType && owner.gatewayClientType !== policy.gatewayClient.type) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
    }
    if (owner?.gatewayClientId && owner.gatewayClientId !== policy.gatewayClient.id) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
    }
    return {
      ...owner,
      gatewayClientType: policy.gatewayClient.type,
      gatewayClientId: policy.gatewayClient.id,
    };
  }

  private resolveMailOwner(
    auth: TAuthContext,
    owner: plugins.servezoneInterfaces.data.IMailResourceOwner,
  ): plugins.servezoneInterfaces.data.IMailResourceOwner {
    const resolvedOwner = this.resolveMailOwnerFilter(auth, owner);
    if (!resolvedOwner?.gatewayClientType || !resolvedOwner.gatewayClientId) {
      throw new plugins.typedrequest.TypedResponseError('mail owner is missing gateway client type or id');
    }
    return resolvedOwner as plugins.servezoneInterfaces.data.IMailResourceOwner;
  }

  private assertGatewayClientOwnership(auth: TAuthContext, ownership: Required<plugins.servezoneInterfaces.data.IGatewayClientOwnership>): void {
    const policy = auth.token?.policy;
    if (!policy || policy.role !== 'gatewayClient') return;
    if (!this.matchesHostnamePatterns(ownership.hostname, policy.hostnamePatterns || [])) {
      throw new plugins.typedrequest.TypedResponseError('hostname is outside token policy');
    }
  }

  private assertRouteTargetsAllowed(auth: TAuthContext, route?: interfaces.data.IDcRouterRouteConfig): void {
    const policy = auth.token?.policy;
    if (!policy || policy.role !== 'gatewayClient' || !route) return;
    const allowedTargets = policy.allowedRouteTargets || [];
    if (allowedTargets.length === 0) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token has no allowed route targets');
    }
    const targets = ((route.action as any)?.targets || []) as Array<{ host?: string; port?: number }>;
    for (const target of targets) {
      const host = String(target.host || '').trim().toLowerCase();
      const port = Number(target.port);
      const allowed = allowedTargets.some((allowedTarget) => {
        return allowedTarget.host.trim().toLowerCase() === host && allowedTarget.ports.includes(port);
      });
      if (!allowed) {
        throw new plugins.typedrequest.TypedResponseError(`route target is outside token policy: ${host}:${port}`);
      }
    }
  }

  private assertMailForwardTargetAllowed(
    auth: TAuthContext,
    target?: plugins.servezoneInterfaces.data.IMailInboundTarget,
  ): void {
    const policy = auth.token?.policy;
    if (!policy || policy.role !== 'gatewayClient' || !target?.smtpForward) return;
    const allowedTargets = policy.allowedRouteTargets || [];
    if (allowedTargets.length === 0) {
      throw new plugins.typedrequest.TypedResponseError('gateway client token has no allowed route targets');
    }
    const host = target.smtpForward.host.trim().toLowerCase();
    const port = Number(target.smtpForward.port);
    const allowed = allowedTargets.some((allowedTarget) => {
      return allowedTarget.host.trim().toLowerCase() === host && allowedTarget.ports.includes(port);
    });
    if (!allowed) {
      throw new plugins.typedrequest.TypedResponseError(`mail target is outside token policy: ${host}:${port}`);
    }
  }

  private matchesHostnamePatterns(hostname: string, patterns: string[]): boolean {
    const normalizedHostname = hostname.trim().toLowerCase();
    if (!normalizedHostname) return false;
    for (const pattern of patterns) {
      const normalizedPattern = pattern.trim().toLowerCase();
      if (!normalizedPattern) continue;
      if (normalizedPattern === normalizedHostname) return true;
      if (normalizedPattern.startsWith('*.')) {
        const suffix = normalizedPattern.slice(2);
        if (!normalizedHostname.endsWith(`.${suffix}`)) continue;
        const prefix = normalizedHostname.slice(0, -(suffix.length + 1));
        if (prefix && !prefix.includes('.')) return true;
      }
    }
    return false;
  }

  private getRouteHostnames(route: interfaces.data.IDcRouterRouteConfig): string[] {
    const domains = (route.match as any)?.domains;
    if (Array.isArray(domains)) {
      return domains.map((domain) => String(domain).trim().toLowerCase()).filter(Boolean);
    }
    if (typeof domains === 'string') {
      return domains.split(',').map((domain) => domain.trim().toLowerCase()).filter(Boolean);
    }
    return [];
  }

  private getOwnedRoutes(gatewayClientId?: string): interfaces.data.IMergedRoute[] {
    const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
    if (!manager) return [];
    return manager.getMergedRoutes().routes.filter((route) => {
      const metadata = route.metadata;
      if (!metadata) return false;
      if (metadata.ownerType !== 'gatewayClient') return false;
      return gatewayClientId ? metadata.gatewayClientId === gatewayClientId : true;
    });
  }

  private async listGatewayClientDomains(
    auth: TAuthContext,
    requestedGatewayClientId?: string,
  ): Promise<plugins.servezoneInterfaces.data.IGatewayDomain[]> {
    const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
    if (!dnsManager) return [];
    const gatewayClientId = this.resolveGatewayClientId(auth, requestedGatewayClientId);
    const ownedRoutes = this.getOwnedRoutes(gatewayClientId);
    const routeHostnames = ownedRoutes.flatMap((route) => this.getRouteHostnames(route.route));
    const docs = await dnsManager.listDomains();

    return docs
      .filter((domainDoc) => {
        if (!auth.token?.policy || auth.token.policy.role !== 'gatewayClient') return true;
        return routeHostnames.some((hostname) => this.isHostnameInDomain(hostname, domainDoc.name));
      })
      .map((domainDoc) => {
        const domain = dnsManager.toPublicDomain(domainDoc);
        const canManageDnsRecords = domain.source === 'dcrouter' || Boolean(domain.providerId);
        const serviceCount = routeHostnames.filter((hostname) => this.isHostnameInDomain(hostname, domain.name)).length;
        return {
          ...domain,
          serviceCount,
          managePath: `/domains/${domain.id}`,
          capabilities: {
            canCreateSubdomains: canManageDnsRecords,
            canManageDnsRecords,
            canIssueCertificates: Boolean(this.opsServerRef.dcRouterRef.smartProxy),
            canHostEmail: Boolean(this.opsServerRef.dcRouterRef.emailDomainManager),
          },
        } satisfies plugins.servezoneInterfaces.data.IGatewayDomain;
      });
  }

  private async listGatewayClientDnsRecords(
    auth: TAuthContext,
    requestedGatewayClientId?: string,
  ): Promise<plugins.servezoneInterfaces.data.IGatewayDnsRecord[]> {
    const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
    if (!dnsManager) return [];
    const gatewayClientId = this.resolveGatewayClientId(auth, requestedGatewayClientId);
    const ownedRoutes = this.getOwnedRoutes(gatewayClientId);
    const domains = await dnsManager.listDomains();
    const records: plugins.servezoneInterfaces.data.IGatewayDnsRecord[] = [];

    for (const route of ownedRoutes) {
      const metadata = route.metadata;
      if (!metadata) continue;
      const gatewayClientType = metadata.gatewayClientType || 'custom';
      const routeGatewayClientId = metadata.gatewayClientId || '';
      const appId = metadata.gatewayClientAppId || '';

      for (const hostname of this.getRouteHostnames(route.route)) {
        if (auth.token?.policy?.role === 'gatewayClient' && !this.matchesHostnamePatterns(hostname, auth.token.policy.hostnamePatterns || [])) {
          continue;
        }
        const domainDoc = domains.find((domain) => this.isHostnameInDomain(hostname, domain.name));
        const domainRecords = domainDoc ? await dnsManager.listRecordsForDomain(domainDoc.id) : [];
        const matchingRecords = domainRecords.filter((record) => record.name === hostname);
        if (matchingRecords.length === 0) {
          records.push({
            id: `missing:${hostname}`,
            domainId: domainDoc?.id || '',
            domainName: domainDoc?.name,
            name: hostname,
            type: 'MISSING',
            value: '',
            ttl: 0,
            source: 'local',
            status: 'missing',
            gatewayClientType,
            gatewayClientId: routeGatewayClientId,
            appId,
            hostname,
            routeId: route.id,
            managePath: domainDoc ? `/domains/${domainDoc.id}/dns` : '/domains',
            createdAt: route.createdAt || 0,
            updatedAt: route.updatedAt || 0,
            createdBy: '',
          });
          continue;
        }
        for (const recordDoc of matchingRecords) {
          const record = dnsManager.toPublicRecord(recordDoc);
          records.push({
            ...record,
            domainName: domainDoc?.name,
            status: 'active',
            gatewayClientType,
            gatewayClientId: routeGatewayClientId,
            appId,
            hostname,
            routeId: route.id,
            managePath: `/dns-records/${record.id}`,
          });
        }
      }
    }

    return records;
  }

  private isHostnameInDomain(hostname: string, domainName: string): boolean {
    const normalizedHostname = hostname.trim().toLowerCase();
    const normalizedDomainName = domainName.trim().toLowerCase();
    return normalizedHostname === normalizedDomainName || normalizedHostname.endsWith(`.${normalizedDomainName}`);
  }

  private async syncGatewayClientRoute(
    auth: TAuthContext,
    ownership: plugins.servezoneInterfaces.data.IGatewayClientOwnership,
    route?: plugins.servezoneInterfaces.data.IGatewayRouteConfig,
    enabled?: boolean,
    deleteRoute?: boolean,
    sourceProfileRef?: string,
  ): Promise<plugins.servezoneInterfaces.data.IGatewayRouteSyncResult> {
    const resolvedOwnership = this.resolveGatewayClientOwnership(auth, ownership);
    this.assertGatewayClientOwnership(auth, resolvedOwnership);
    this.assertRouteTargetsAllowed(auth, route);

    const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
    if (!manager) {
      return { success: false, message: 'Route management not initialized' };
    }

    const externalKey = this.buildGatewayClientExternalKey(resolvedOwnership);
    const existingRoute = manager.findApiRouteByExternalKey(externalKey);

    if (deleteRoute) {
      if (!existingRoute) {
        return { success: true, action: 'unchanged' };
      }
      const result = await manager.deleteRoute(existingRoute.id);
      return result.success
        ? { success: true, action: 'deleted', routeId: existingRoute.id }
        : { success: false, message: result.message };
    }

    if (!route) {
      return { success: false, message: 'route is required unless delete=true' };
    }

    const sourceBindings = this.getManagedRouteSourceBindings(sourceProfileRef);
    if (!sourceBindings) {
      return {
        success: false,
        message: sourceProfileRef
          ? `source profile '${sourceProfileRef}' not found`
          : 'PUBLIC source profile not found',
      };
    }

    const metadata: interfaces.data.IRouteMetadata = {
      sourceBindings,
      ownerType: 'gatewayClient',
      gatewayClientType: resolvedOwnership.gatewayClientType,
      gatewayClientId: resolvedOwnership.gatewayClientId,
      gatewayClientAppId: resolvedOwnership.appId,
      externalKey,
    };
    const normalizedRoute = this.normalizeGatewayClientRoute(route, resolvedOwnership, externalKey);

    if (existingRoute) {
      const routePatch: Partial<interfaces.data.IDcRouterRouteConfig> = { ...normalizedRoute };
      (routePatch as any).security = null;
      const result = await manager.updateRoute(existingRoute.id, {
        route: routePatch,
        enabled: enabled ?? true,
        metadata,
      });
      return result.success
        ? { success: true, action: 'updated', routeId: existingRoute.id }
        : { success: false, message: result.message };
    }

    const routeId = await manager.createRoute(normalizedRoute, auth.userId, enabled ?? true, metadata);
    return { success: true, action: 'created', routeId };
  }

  private buildGatewayClientExternalKey(ownership: Required<plugins.servezoneInterfaces.data.IGatewayClientOwnership>): string {
    return [
      ownership.gatewayClientType,
      ownership.gatewayClientId,
      ownership.appId,
      ownership.hostname,
    ].map((part) => part.trim()).join(':');
  }


  private normalizeGatewayClientRoute(
    route: interfaces.data.IDcRouterRouteConfig,
    ownership: Required<plugins.servezoneInterfaces.data.IGatewayClientOwnership>,
    externalKey: string,
  ): interfaces.data.IDcRouterRouteConfig {
    const normalizedRoute = structuredClone(route);
    delete normalizedRoute.security;
    if (!normalizedRoute.name) {
      normalizedRoute.name = `gateway-client-${externalKey.replace(/[^a-zA-Z0-9-]+/g, '-').slice(0, 80)}`;
    }
    return normalizedRoute;
  }

  private getManagedRouteSourceBindings(
    requestedProfileRefArg?: string,
  ): interfaces.data.IRouteSourceBinding[] | undefined {
    const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
    const profiles = resolver?.listProfiles() || [];
    const wantedRef = (requestedProfileRefArg || 'public').trim().toLowerCase();
    const profile = profiles.find((profileArg: interfaces.data.ISourceProfile) => {
      return profileArg.id.trim().toLowerCase() === wantedRef;
    }) || profiles.find((profileArg: interfaces.data.ISourceProfile) => {
      return profileArg.name.trim().toLowerCase() === wantedRef;
    });
    if (!profile) {
      return undefined;
    }
    return [{
      sourceProfileRef: profile.id,
      sourceProfileName: profile.name,
    }];
  }
}
