import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IModbusManualTcpEntry } from './modbus.types.js';

const modbusDomain = 'modbus';
const defaultModbusTcpPort = 502;

export class ModbusManualTcpMatcher implements IDiscoveryMatcher<IModbusManualTcpEntry> {
  public id = 'modbus-manual-tcp-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Modbus TCP setup entries without probing serial ports.';

  public async matches(inputArg: IModbusManualTcpEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    if (inputArg.type === 'serial' || this.looksLikeSerialPort(inputArg.host) || this.looksLikeSerialPort(String(inputArg.port || ''))) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'Serial RTU setup is not auto-discovered by this integration.',
      };
    }

    const haystack = `${inputArg.name || ''} ${inputArg.protocol || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
    const hasModbusHint = inputArg.type === 'tcp' || haystack.includes('modbus') || Boolean(inputArg.metadata?.modbus || inputArg.metadata?.modbusTcp);
    const matched = Boolean(inputArg.host && (hasModbusHint || inputArg.port === defaultModbusTcpPort));
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'Manual entry does not contain a Modbus TCP host or hint.',
      };
    }

    const id = this.normalizedId(inputArg);
    return {
      matched: true,
      confidence: hasModbusHint ? 'high' : 'medium',
      reason: 'Manual entry can be configured as a Modbus TCP hub.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: modbusDomain,
        id,
        host: inputArg.host,
        port: inputArg.port || defaultModbusTcpPort,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer || 'Modbus',
        model: inputArg.model || 'TCP hub',
        serialNumber: inputArg.serialNumber,
        metadata: {
          ...inputArg.metadata,
          type: 'tcp',
          unitId: inputArg.unitId || inputArg.slave || 1,
        },
      },
    };
  }

  private normalizedId(inputArg: IModbusManualTcpEntry): string {
    return slug(inputArg.id || inputArg.serialNumber || `${inputArg.host || 'modbus'}_${inputArg.port || defaultModbusTcpPort}`);
  }

  private looksLikeSerialPort(valueArg: string | undefined): boolean {
    return Boolean(valueArg && (/^\/dev\//.test(valueArg) || /^com\d+$/i.test(valueArg)));
  }
}

export class ModbusCandidateValidator implements IDiscoveryValidator {
  public id = 'modbus-candidate-validator';
  public description = 'Validate that a candidate has enough Modbus TCP information for setup.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const type = String(candidateArg.metadata?.type || '').toLowerCase();
    if (type === 'serial' || this.looksLikeSerialPort(candidateArg.host)) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'Serial RTU candidates require explicit serial configuration and are not probed.',
      };
    }

    const haystack = `${candidateArg.name || ''} ${candidateArg.model || ''} ${candidateArg.manufacturer || ''}`.toLowerCase();
    const matched = Boolean(candidateArg.host && (
      candidateArg.integrationDomain === modbusDomain
      || candidateArg.port === defaultModbusTcpPort
      || type === 'tcp'
      || haystack.includes('modbus')
      || candidateArg.metadata?.modbus
      || candidateArg.metadata?.modbusTcp
    ));
    const id = slug(candidateArg.id || candidateArg.serialNumber || `${candidateArg.host || 'modbus'}_${candidateArg.port || defaultModbusTcpPort}`);
    return {
      matched,
      confidence: matched && candidateArg.integrationDomain === modbusDomain ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Modbus TCP setup data.' : 'Candidate is not a Modbus TCP hub.',
      normalizedDeviceId: matched ? id : undefined,
      candidate: matched ? {
        ...candidateArg,
        integrationDomain: modbusDomain,
        id,
        port: candidateArg.port || defaultModbusTcpPort,
        manufacturer: candidateArg.manufacturer || 'Modbus',
        model: candidateArg.model || 'TCP hub',
        metadata: {
          ...candidateArg.metadata,
          type: 'tcp',
        },
      } : undefined,
    };
  }

  private looksLikeSerialPort(valueArg: string | undefined): boolean {
    return Boolean(valueArg && (/^\/dev\//.test(valueArg) || /^com\d+$/i.test(valueArg)));
  }
}

export const createModbusDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: modbusDomain, displayName: 'Modbus' })
    .addMatcher(new ModbusManualTcpMatcher())
    .addValidator(new ModbusCandidateValidator());
};

const slug = (valueArg: string): string => valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || modbusDomain;
