import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IKnxGatewayDescriptor, IKnxManualDiscoveryEntry, IKnxMdnsRecord, TKnxConnectionType } from './knx.types.js';

const defaultKnxPort = 3671;

export class KnxManualMatcher implements IDiscoveryMatcher<IKnxManualDiscoveryEntry> {
  public id = 'knx-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual KNX/IP connection entries.';

  public async matches(inputArg: IKnxManualDiscoveryEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const connectionType = this.connectionType(inputArg.connectionType || inputArg.connection_type, inputArg.host);
    const model = inputArg.model?.toLowerCase() || '';
    const metadataKnx = inputArg.metadata?.knx === true;
    const matched = Boolean(inputArg.host || connectionType !== 'automatic' || metadataKnx || model.includes('knx'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain KNX/IP setup hints.' };
    }
    return {
      matched: true,
      confidence: inputArg.host || inputArg.gateway ? 'high' : 'medium',
      reason: 'Manual entry can configure a KNX/IP connection.',
      normalizedDeviceId: inputArg.id || inputArg.host || inputArg.individualAddress || inputArg.individual_address,
      candidate: {
        source: 'manual',
        integrationDomain: 'knx',
        id: inputArg.id || inputArg.host || inputArg.individualAddress || inputArg.individual_address,
        host: inputArg.host || this.gatewayHost(inputArg.gateway),
        port: inputArg.port || inputArg.gateway?.port || defaultKnxPort,
        name: inputArg.name || inputArg.gateway?.name || 'KNX/IP interface',
        manufacturer: inputArg.manufacturer || 'KNX Association',
        model: inputArg.model || this.connectionLabel(connectionType),
        metadata: {
          ...inputArg.metadata,
          knx: true,
          connectionType,
          individualAddress: inputArg.individualAddress || inputArg.individual_address,
          localIp: inputArg.localIp || inputArg.local_ip,
          multicastGroup: inputArg.multicastGroup || inputArg.multicast_group,
          multicastPort: inputArg.multicastPort || inputArg.multicast_port,
          routeBack: inputArg.routeBack ?? inputArg.route_back,
          gateway: inputArg.gateway,
        },
      },
    };
  }

  private connectionType(valueArg: unknown, hostArg?: string): TKnxConnectionType {
    return typeof valueArg === 'string' && valueArg ? valueArg : hostArg ? 'tunneling' : 'automatic';
  }

  private gatewayHost(gatewayArg?: IKnxGatewayDescriptor): string | undefined {
    return gatewayArg?.host || gatewayArg?.ipAddr || gatewayArg?.ip_addr || gatewayArg?.ipAddress;
  }

  private connectionLabel(connectionTypeArg: TKnxConnectionType): string {
    return `KNX/IP ${String(connectionTypeArg).replace(/_/g, ' ')}`;
  }
}

export class KnxGatewayDescriptorMatcher implements IDiscoveryMatcher<IKnxGatewayDescriptor> {
  public id = 'knx-gateway-descriptor-match';
  public source = 'custom' as const;
  public description = 'Recognize xknx GatewayDescriptor-like KNXnet/IP discovery results.';

  public async matches(inputArg: IKnxGatewayDescriptor, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const host = this.gatewayHost(inputArg);
    const supportsRouting = inputArg.supportsRouting ?? inputArg.supports_routing ?? false;
    const supportsTunneling = inputArg.supportsTunneling ?? inputArg.supportsTunnelling ?? inputArg.supports_tunnelling ?? false;
    const supportsTunnelingTcp = inputArg.supportsTunnelingTcp ?? inputArg.supportsTunnellingTcp ?? inputArg.supports_tunnelling_tcp ?? false;
    const supportsSecure = inputArg.supportsSecure ?? inputArg.supports_secure ?? false;
    const matched = Boolean(host && (inputArg.port === defaultKnxPort || supportsRouting || supportsTunneling || supportsTunnelingTcp || supportsSecure));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Gateway descriptor does not describe a KNXnet/IP interface.' };
    }
    const connectionType = this.connectionType(inputArg);
    const individualAddress = this.individualAddressString(inputArg.individualAddress || inputArg.individual_address);
    return {
      matched: true,
      confidence: supportsRouting || supportsTunneling || supportsTunnelingTcp ? 'certain' : 'high',
      reason: 'Gateway descriptor exposes KNXnet/IP service families.',
      normalizedDeviceId: inputArg.id || individualAddress || host,
      candidate: {
        source: 'custom',
        integrationDomain: 'knx',
        id: inputArg.id || individualAddress || host,
        host,
        port: inputArg.port || defaultKnxPort,
        name: inputArg.name || 'KNX/IP interface',
        manufacturer: 'KNX Association',
        model: this.connectionLabel(connectionType),
        metadata: {
          knx: true,
          connectionType,
          individualAddress,
          localIp: inputArg.localIp || inputArg.local_ip,
          localInterface: inputArg.localInterface || inputArg.local_interface,
          supportsRouting,
          supportsTunneling,
          supportsTunnelingTcp,
          supportsSecure,
          routingRequiresSecure: inputArg.routingRequiresSecure ?? inputArg.routing_requires_secure,
          tunnelingRequiresSecure: inputArg.tunnelingRequiresSecure ?? inputArg.tunnellingRequiresSecure ?? inputArg.tunnelling_requires_secure,
          tunnelingSlots: inputArg.tunnelingSlots || inputArg.tunnellingSlots || inputArg.tunnelling_slots,
          descriptor: inputArg,
        },
      },
    };
  }

  private connectionType(gatewayArg: IKnxGatewayDescriptor): TKnxConnectionType {
    const tunnelingRequiresSecure = gatewayArg.tunnelingRequiresSecure ?? gatewayArg.tunnellingRequiresSecure ?? gatewayArg.tunnelling_requires_secure;
    const routingRequiresSecure = gatewayArg.routingRequiresSecure ?? gatewayArg.routing_requires_secure;
    if (tunnelingRequiresSecure) {
      return 'tunneling_tcp_secure';
    }
    if (gatewayArg.supportsTunnelingTcp || gatewayArg.supportsTunnellingTcp || gatewayArg.supports_tunnelling_tcp) {
      return 'tunneling_tcp';
    }
    if (gatewayArg.supportsTunneling || gatewayArg.supportsTunnelling || gatewayArg.supports_tunnelling) {
      return 'tunneling';
    }
    if (routingRequiresSecure) {
      return 'routing_secure';
    }
    if (gatewayArg.supportsRouting || gatewayArg.supports_routing) {
      return 'routing';
    }
    return 'automatic';
  }

  private gatewayHost(gatewayArg: IKnxGatewayDescriptor): string | undefined {
    return gatewayArg.host || gatewayArg.ipAddr || gatewayArg.ip_addr || gatewayArg.ipAddress;
  }

  private individualAddressString(valueArg: unknown): string | undefined {
    if (typeof valueArg === 'string') {
      return valueArg;
    }
    if (valueArg && typeof valueArg === 'object' && 'toString' in valueArg) {
      const rendered = String(valueArg);
      return rendered === '[object Object]' ? undefined : rendered;
    }
    return undefined;
  }

  private connectionLabel(connectionTypeArg: TKnxConnectionType): string {
    return `KNX/IP ${String(connectionTypeArg).replace(/_/g, ' ')}`;
  }
}

export class KnxMdnsMatcher implements IDiscoveryMatcher<IKnxMdnsRecord> {
  public id = 'knx-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize KNXnet/IP mDNS service records when a discovery broker provides them.';

  public async matches(inputArg: IKnxMdnsRecord, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const service = `${inputArg.type || ''} ${inputArg.name || ''}`.toLowerCase();
    const txt = inputArg.txt || inputArg.properties || {};
    const matched = service.includes('knx') || service.includes('knxnet-ip') || txt.knx === 'true';
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a KNXnet/IP service.' };
    }
    const host = inputArg.host || inputArg.hostname || inputArg.addresses?.[0];
    return {
      matched: true,
      confidence: host ? 'high' : 'medium',
      reason: 'mDNS service name indicates KNXnet/IP.',
      normalizedDeviceId: txt.id || host,
      candidate: {
        source: 'mdns',
        integrationDomain: 'knx',
        id: txt.id || host,
        host,
        port: inputArg.port || defaultKnxPort,
        name: inputArg.name || 'KNX/IP interface',
        manufacturer: 'KNX Association',
        model: 'KNX/IP interface',
        metadata: {
          knx: true,
          connectionType: txt.connection_type || txt.connectionType || 'automatic',
          txt,
        },
      },
    };
  }
}

export class KnxCandidateValidator implements IDiscoveryValidator {
  public id = 'knx-candidate-validator';
  public description = 'Validate KNX/IP gateway or manual setup candidates.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadataConnectionType = candidateArg.metadata?.connectionType || candidateArg.metadata?.connection_type;
    const matched = candidateArg.integrationDomain === 'knx'
      || candidateArg.metadata?.knx === true
      || candidateArg.port === defaultKnxPort
      || typeof metadataConnectionType === 'string' && ['automatic', 'routing', 'routing_secure', 'tunneling', 'tunneling_tcp', 'tunneling_tcp_secure'].includes(metadataConnectionType);
    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has KNX/IP metadata.' : 'Candidate is not KNX/IP.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id || candidateArg.host,
    };
  }
}

export const createKnxDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'knx', displayName: 'KNX' })
    .addMatcher(new KnxManualMatcher())
    .addMatcher(new KnxGatewayDescriptorMatcher())
    .addMatcher(new KnxMdnsMatcher())
    .addValidator(new KnxCandidateValidator());
};
