import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IZwaveJsManualEntry, IZwaveJsMdnsRecord, IZwaveJsUsbRecord } from './zwave_js.types.js';

const knownUsbIds = new Set(['0658:0200', '10c4:8a2a', '303a:4001', '10c4:ea60']);

export class ZwaveJsMdnsMatcher implements IDiscoveryMatcher<IZwaveJsMdnsRecord> {
  public id = 'zwave-js-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Z-Wave JS Server mDNS advertisements.';

  public async matches(recordArg: IZwaveJsMdnsRecord): Promise<IDiscoveryMatch> {
    const type = (recordArg.type || '').toLowerCase().replace(/\.$/, '');
    const matched = type === '_zwave-js-server._tcp.local' || Boolean(recordArg.name?.toLowerCase().includes('zwave-js'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a Z-Wave JS Server advertisement.' };
    }
    return {
      matched: true,
      confidence: recordArg.host ? 'certain' : 'high',
      reason: 'mDNS record matches Z-Wave JS Server metadata.',
      normalizedDeviceId: recordArg.name,
      candidate: {
        source: 'mdns',
        integrationDomain: 'zwave_js',
        id: recordArg.name,
        host: recordArg.host,
        port: recordArg.port || 3000,
        name: 'Z-Wave JS Server',
        manufacturer: 'Z-Wave JS',
        model: 'WebSocket Server',
        metadata: { mdnsName: recordArg.name, mdnsType: recordArg.type, txt: recordArg.txt, url: recordArg.host ? `ws://${recordArg.host}:${recordArg.port || 3000}` : undefined },
      },
    };
  }
}

export class ZwaveJsUsbMatcher implements IDiscoveryMatcher<IZwaveJsUsbRecord> {
  public id = 'zwave-js-usb-match';
  public source = 'usb' as const;
  public description = 'Recognize known Z-Wave USB adapters used by Z-Wave JS.';

  public async matches(recordArg: IZwaveJsUsbRecord): Promise<IDiscoveryMatch> {
    const usbId = `${(recordArg.vid || '').toLowerCase()}:${(recordArg.pid || '').toLowerCase()}`;
    const description = `${recordArg.manufacturer || ''} ${recordArg.description || ''}`.toLowerCase();
    const matched = knownUsbIds.has(usbId) || description.includes('z-wave') || description.includes('zwave') || description.includes('zwa-2');
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'USB device is not a known Z-Wave adapter.' };
    }
    return {
      matched: true,
      confidence: knownUsbIds.has(usbId) ? 'certain' : 'high',
      reason: 'USB record matches known Z-Wave adapter metadata.',
      normalizedDeviceId: recordArg.serialNumber || recordArg.path || usbId,
      candidate: {
        source: 'usb',
        integrationDomain: 'zwave_js',
        id: recordArg.serialNumber || recordArg.path || usbId,
        name: recordArg.description || 'Z-Wave adapter',
        manufacturer: recordArg.manufacturer || 'Z-Wave',
        model: recordArg.description,
        serialNumber: recordArg.serialNumber,
        metadata: { vid: recordArg.vid, pid: recordArg.pid, path: recordArg.path },
      },
    };
  }
}

export class ZwaveJsManualMatcher implements IDiscoveryMatcher<IZwaveJsManualEntry> {
  public id = 'zwave-js-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Z-Wave JS setup entries.';

  public async matches(inputArg: IZwaveJsManualEntry): Promise<IDiscoveryMatch> {
    const model = inputArg.model?.toLowerCase() || '';
    const url = inputArg.url || (inputArg.host ? `ws://${inputArg.host}:${inputArg.port || 3000}` : undefined);
    const matched = Boolean(url || inputArg.usbPath || inputArg.metadata?.zwaveJs || model.includes('z-wave') || model.includes('zwave'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Z-Wave JS setup hints.' };
    }
    return {
      matched: true,
      confidence: url ? 'high' : 'medium',
      reason: 'Manual entry can start Z-Wave JS setup.',
      normalizedDeviceId: inputArg.id || url || inputArg.usbPath,
      candidate: {
        source: 'manual',
        integrationDomain: 'zwave_js',
        id: inputArg.id || url || inputArg.usbPath,
        host: inputArg.host,
        port: inputArg.port || 3000,
        name: inputArg.name || 'Z-Wave JS',
        manufacturer: 'Z-Wave JS',
        model: inputArg.model || 'WebSocket Server',
        metadata: { ...inputArg.metadata, url, usbPath: inputArg.usbPath },
      },
    };
  }
}

export class ZwaveJsCandidateValidator implements IDiscoveryValidator {
  public id = 'zwave-js-candidate-validator';
  public description = 'Validate Z-Wave JS candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const model = candidateArg.model?.toLowerCase() || '';
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const matched = candidateArg.integrationDomain === 'zwave_js' || model.includes('z-wave') || model.includes('zwave') || manufacturer.includes('z-wave') || Boolean(candidateArg.metadata?.zwaveJs);
    return {
      matched,
      confidence: matched && (candidateArg.host || candidateArg.metadata?.url || candidateArg.source === 'usb') ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Z-Wave JS metadata.' : 'Candidate is not Z-Wave JS.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createZwaveJsDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'zwave_js', displayName: 'Z-Wave' })
    .addMatcher(new ZwaveJsMdnsMatcher())
    .addMatcher(new ZwaveJsUsbMatcher())
    .addMatcher(new ZwaveJsManualMatcher())
    .addValidator(new ZwaveJsCandidateValidator());
};
