import * as plugins from '../plugins.js';
import type { IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
import type { ISecurityCompiledPolicy } from '../../ts_interfaces/data/security-policy.js';
import type { DcRouter } from '../classes.dcrouter.js';

type TInboundProxyProtocolPolicy = NonNullable<plugins.smartproxy.IRouteMatch['inboundProxyProtocol']>;

/**
 * Pure route-policy transformations applied before routes reach SmartProxy:
 * per-listener inbound PROXY protocol policies (driven by RemoteIngress and
 * VPN) and merging of compiled security policies.
 */
export class RoutePolicyAugmenter {
  constructor(private dcRouterRef: DcRouter) {}

  /**
   * Ensure every route on a shared listener carries a compatible
   * inboundProxyProtocol policy, deriving defaults from RemoteIngress/VPN.
   */
  public applyInboundProxyProtocolPolicies(
    routes: plugins.smartproxy.IRouteConfig[],
  ): plugins.smartproxy.IRouteConfig[] {
    const policiesByListener = new Map<string, TInboundProxyProtocolPolicy>();

    for (const route of routes) {
      const policy = route.match?.inboundProxyProtocol || this.getDesiredInboundProxyProtocolPolicy(route);
      if (!policy) {
        continue;
      }
      for (const listenerKey of this.getInboundProxyListenerKeys(route)) {
        const mergedPolicy = this.mergeInboundProxyProtocolPolicies(
          policiesByListener.get(listenerKey),
          policy,
        );
        if (mergedPolicy) {
          policiesByListener.set(listenerKey, mergedPolicy);
        }
      }
    }

    if (policiesByListener.size === 0) {
      return routes;
    }

    return routes.map((route) => {
      if (route.match?.inboundProxyProtocol) {
        return route;
      }
      let listenerPolicy: TInboundProxyProtocolPolicy | undefined;
      for (const listenerKey of this.getInboundProxyListenerKeys(route)) {
        listenerPolicy = this.mergeInboundProxyProtocolPolicies(
          listenerPolicy,
          policiesByListener.get(listenerKey),
        );
      }
      if (!listenerPolicy) {
        return route;
      }
      return {
        ...route,
        match: {
          ...route.match,
          inboundProxyProtocol: listenerPolicy,
        },
      };
    });
  }

  /** Union blocked IPs/CIDRs across configured and compiled security policies. */
  public mergeSecurityPolicies(
    ...policies: Array<Partial<ISecurityCompiledPolicy> | undefined>
  ): ISecurityCompiledPolicy | undefined {
    const blockedIps = new Set<string>();
    const blockedCidrs = new Set<string>();

    for (const policy of policies) {
      for (const ip of policy?.blockedIps || []) {
        if (ip) blockedIps.add(ip);
      }
      for (const cidr of policy?.blockedCidrs || []) {
        if (cidr) blockedCidrs.add(cidr);
      }
    }

    if (blockedIps.size === 0 && blockedCidrs.size === 0) {
      return undefined;
    }

    return {
      blockedIps: [...blockedIps].sort(),
      blockedCidrs: [...blockedCidrs].sort(),
    };
  }

  private getDesiredInboundProxyProtocolPolicy(
    route: plugins.smartproxy.IRouteConfig,
  ): TInboundProxyProtocolPolicy | undefined {
    const dcRoute = route as IDcRouterRouteConfig;
    if (this.dcRouterRef.isRemoteIngressHubEnabled() && dcRoute.remoteIngress?.enabled) {
      const ports = plugins.smartproxy.expandPortRange(route.match.ports as any) as number[];
      if (ports.some((port) => port === 25 || port === 587)) {
        return { mode: 'required' };
      }
      return { mode: 'optional' };
    }
    if (this.dcRouterRef.options.vpnConfig?.enabled) {
      return { mode: 'optional' };
    }
    return undefined;
  }

  private getInboundProxyListenerKeys(route: plugins.smartproxy.IRouteConfig): string[] {
    const ports = plugins.smartproxy.expandPortRange(route.match.ports as any) as number[];
    const transports = route.match.transport === 'udp'
      ? ['udp']
      : route.match.transport === 'all'
        ? ['tcp', 'udp']
        : ['tcp'];
    const keys: string[] = [];
    for (const port of ports) {
      for (const transport of transports) {
        keys.push(`${transport}:${port}`);
      }
    }
    return keys;
  }

  private mergeInboundProxyProtocolPolicies(
    current?: TInboundProxyProtocolPolicy,
    next?: TInboundProxyProtocolPolicy,
  ): TInboundProxyProtocolPolicy | undefined {
    if (!current) return next;
    if (!next) return current;
    if (current.mode === 'required') return current;
    if (next.mode === 'required') return next;
    if (current.mode === 'optional') return current;
    if (next.mode === 'optional') return next;
    return current;
  }
}
