import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { wakeOnLanDefaultBroadcastAddress, wakeOnLanDefaultBroadcastPort, wakeOnLanDefaultName, wakeOnLanDomain } from './wake_on_lan.constants.js';
import { normalizeWakeOnLanMac } from './wake_on_lan.packet.js';
import type { IWakeOnLanCandidateMetadata, IWakeOnLanManualEntry, IWakeOnLanSnapshot, IWakeOnLanStaticHostRecord, TWakeOnLanEntityMode } from './wake_on_lan.types.js';

export class WakeOnLanManualMatcher implements IDiscoveryMatcher<IWakeOnLanManualEntry> {
  public id = 'wake-on-lan-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Wake on LAN MAC/host setup entries and snapshot records.';

  public async matches(inputArg: IWakeOnLanManualEntry): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const snapshot = inputArg.snapshot || metadata.snapshot as IWakeOnLanSnapshot | undefined;
    const snapshotTarget = snapshot?.targets.find((targetArg) => normalizeWakeOnLanMac(targetArg.macAddress || targetArg.mac || targetArg.normalizedMac));
    const macAddress = normalizeWakeOnLanMac(inputArg.macAddress || inputArg.mac || stringValue(metadata.macAddress) || stringValue(metadata.mac) || snapshotTarget?.macAddress || snapshotTarget?.mac || snapshotTarget?.normalizedMac);
    const host = inputArg.host || snapshotTarget?.host || stringValue(metadata.host);
    const matched = inputArg.integrationDomain === wakeOnLanDomain
      || metadata.wakeOnLan === true
      || metadata.wake_on_lan === true
      || Boolean(snapshot)
      || Boolean(macAddress);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Wake on LAN setup data.' };
    }

    const id = inputArg.id || macAddress || host;
    return {
      matched: true,
      confidence: macAddress && host ? 'certain' : macAddress || snapshot ? 'high' : 'medium',
      reason: snapshot ? 'Manual entry includes a Wake on LAN snapshot.' : 'Manual entry includes a Wake on LAN MAC or host target.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: wakeOnLanDomain,
        id,
        host,
        port: inputArg.broadcastPort || inputArg.broadcast_port || wakeOnLanDefaultBroadcastPort,
        name: inputArg.name || snapshotTarget?.name || (macAddress ? `${wakeOnLanDefaultName} ${macAddress}` : wakeOnLanDefaultName),
        manufacturer: inputArg.manufacturer || 'Wake on LAN',
        model: inputArg.model || 'Magic Packet Target',
        macAddress,
        metadata: {
          ...metadata,
          wakeOnLan: true,
          host,
          macAddress,
          broadcastAddress: inputArg.broadcastAddress || inputArg.broadcast_address || wakeOnLanDefaultBroadcastAddress,
          broadcastPort: inputArg.broadcastPort || inputArg.broadcast_port || wakeOnLanDefaultBroadcastPort,
          entityMode: inputArg.platform || 'button',
          snapshot,
          nativeUdpImplemented: true,
        } satisfies IWakeOnLanCandidateMetadata,
      },
      metadata: { macConfigured: Boolean(macAddress), hostConfigured: Boolean(host), snapshotConfigured: Boolean(snapshot) },
    };
  }
}

export class WakeOnLanStaticHostMatcher implements IDiscoveryMatcher<IWakeOnLanStaticHostRecord> {
  public id = 'wake-on-lan-static-host-match';
  public source = 'manual' as const;
  public description = 'Recognize static host/MAC records as Wake on LAN setup candidates.';

  public async matches(recordArg: IWakeOnLanStaticHostRecord): Promise<IDiscoveryMatch> {
    const metadata = recordArg.metadata || {};
    const host = recordArg.host || recordArg.ipAddress || recordArg.address || recordArg.ip || stringValue(metadata.host);
    const hostname = recordArg.hostname || recordArg.hostName;
    const macAddress = normalizeWakeOnLanMac(recordArg.macAddress || recordArg.mac || stringValue(metadata.macAddress) || stringValue(metadata.mac));
    const matched = recordArg.integrationDomain === wakeOnLanDomain || metadata.wakeOnLan === true || metadata.wake_on_lan === true || Boolean(host && macAddress);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Static host record does not include both host and MAC data for Wake on LAN.' };
    }

    const id = macAddress || host;
    return {
      matched: true,
      confidence: host && macAddress ? 'certain' : macAddress ? 'high' : 'medium',
      reason: 'Static host record includes a Wake on LAN host/MAC target.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: wakeOnLanDomain,
        id,
        host,
        port: recordArg.broadcastPort || recordArg.broadcast_port || wakeOnLanDefaultBroadcastPort,
        name: recordArg.name || hostname || (macAddress ? `${wakeOnLanDefaultName} ${macAddress}` : wakeOnLanDefaultName),
        manufacturer: 'Wake on LAN',
        model: 'Static Magic Packet Target',
        macAddress,
        metadata: {
          ...metadata,
          wakeOnLan: true,
          host,
          hostname,
          macAddress,
          broadcastAddress: recordArg.broadcastAddress || recordArg.broadcast_address || wakeOnLanDefaultBroadcastAddress,
          broadcastPort: recordArg.broadcastPort || recordArg.broadcast_port || wakeOnLanDefaultBroadcastPort,
          entityMode: entityMode(metadata.entityMode) || 'switch',
          nativeUdpImplemented: true,
        } satisfies IWakeOnLanCandidateMetadata,
      },
      metadata: { macConfigured: Boolean(macAddress), hostConfigured: Boolean(host) },
    };
  }
}

export class WakeOnLanCandidateValidator implements IDiscoveryValidator {
  public id = 'wake-on-lan-candidate-validator';
  public description = 'Validate Wake on LAN candidates include a MAC address or snapshot target.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as IWakeOnLanSnapshot | undefined;
    const snapshotMac = snapshot?.targets.map((targetArg) => normalizeWakeOnLanMac(targetArg.macAddress || targetArg.mac || targetArg.normalizedMac)).find(Boolean);
    const macAddress = normalizeWakeOnLanMac(candidateArg.macAddress || stringValue(metadata.macAddress) || stringValue(metadata.mac) || snapshotMac);
    const matched = candidateArg.integrationDomain === wakeOnLanDomain || metadata.wakeOnLan === true || metadata.wake_on_lan === true || Boolean(snapshot) || Boolean(macAddress);
    const usable = Boolean(macAddress || snapshotMac);

    if (!matched || !usable) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'Wake on LAN candidate lacks a valid MAC address.' : 'Candidate is not Wake on LAN.',
      };
    }

    return {
      matched: true,
      confidence: macAddress && candidateArg.host ? 'certain' : 'high',
      reason: 'Candidate has Wake on LAN metadata and a valid MAC address.',
      normalizedDeviceId: candidateArg.id || macAddress || candidateArg.host,
      candidate: {
        ...candidateArg,
        id: candidateArg.id || macAddress || candidateArg.host,
        macAddress,
        port: candidateArg.port || numberValue(metadata.broadcastPort) || wakeOnLanDefaultBroadcastPort,
      },
      metadata: { macConfigured: Boolean(macAddress), nativeUdpImplemented: true },
    };
  }
}

export const createWakeOnLanDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: wakeOnLanDomain, displayName: wakeOnLanDefaultName })
    .addMatcher(new WakeOnLanManualMatcher())
    .addMatcher(new WakeOnLanStaticHostMatcher())
    .addValidator(new WakeOnLanCandidateValidator());
};

const entityMode = (valueArg: unknown): TWakeOnLanEntityMode | undefined => {
  return valueArg === 'button' || valueArg === 'switch' || valueArg === 'both' ? valueArg : undefined;
};

const stringValue = (valueArg: unknown): string | undefined => {
  return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
};

const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return Math.round(valueArg);
  }
  if (typeof valueArg === 'string' && valueArg.trim()) {
    const parsed = Number(valueArg);
    return Number.isFinite(parsed) ? Math.round(parsed) : undefined;
  }
  return undefined;
};
