import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { DevoloHomeNetworkMapper } from './devolo_home_network.mapper.js';
import type { IDevoloManualDiscoveryRecord, IDevoloMdnsRecord } from './devolo_home_network.types.js';
import { devoloHomeNetworkDefaultPort, devoloHomeNetworkDomain, devoloHomeNetworkMdnsType } from './devolo_home_network.types.js';

const homeControlMtNumbers = new Set(['2600', '2601']);
const devoloTextHints = ['devolo', 'dlan', 'magic', 'home network', 'wifi repeater', 'powerline'];

export class DevoloHomeNetworkMdnsMatcher implements IDiscoveryMatcher<IDevoloMdnsRecord> {
  public id = 'devolo-home-network-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize devolo Home Network mDNS device API advertisements.';

  public async matches(recordArg: IDevoloMdnsRecord, contextArg?: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const txt = recordArg.txt || recordArg.properties || {};
    const type = (recordArg.type || recordArg.serviceType || '').toLowerCase();
    const product = this.txt(txt, 'Product') || this.txt(txt, 'product') || this.txt(txt, 'model');
    const mtNumber = this.txt(txt, 'MT') || this.txt(txt, 'mt');
    const serialNumber = this.txt(txt, 'SN') || this.txt(txt, 'sn');
    const host = recordArg.host || recordArg.addresses?.[0];
    const hostname = recordArg.hostname || recordArg.name;
    const text = [type, recordArg.name, hostname, product]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const serviceMatched = type === devoloHomeNetworkMdnsType;
    const textMatched = devoloTextHints.some((hintArg) => text.includes(hintArg));

    if (homeControlMtNumbers.has(mtNumber || '')) {
      return {
        matched: false,
        confidence: 'high',
        reason: 'The devolo Home Control Central Unit is explicitly unsupported by the Home Assistant integration.',
        metadata: { homeControl: true, mtNumber },
      };
    }

    if (!serviceMatched && !textMatched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a devolo Home Network device API advertisement.' };
    }

    const name = this.shortHostname(hostname) || product || 'devolo Home Network';
    return {
      matched: true,
      confidence: serviceMatched && serialNumber ? 'certain' : serviceMatched && host ? 'high' : 'medium',
      reason: serviceMatched ? 'mDNS record advertises the devolo device API service.' : 'mDNS record contains devolo Home Network metadata.',
      normalizedDeviceId: serialNumber || host || recordArg.name,
      candidate: {
        source: 'mdns',
        integrationDomain: devoloHomeNetworkDomain,
        id: serialNumber || recordArg.name || host,
        host,
        port: recordArg.port || devoloHomeNetworkDefaultPort,
        name,
        manufacturer: 'devolo',
        model: product || mtNumber || 'devolo Home Network device',
        serialNumber,
        metadata: {
          mdnsName: recordArg.name,
          mdnsType: recordArg.type || recordArg.serviceType,
          txt,
          Product: product,
          SN: serialNumber,
          MT: mtNumber,
        },
      },
      metadata: { product, serialNumber, mtNumber },
    };
  }

  private txt(txtArg: Record<string, string | undefined>, keyArg: string): string | undefined {
    return txtArg[keyArg] || txtArg[keyArg.toUpperCase()] || txtArg[keyArg.toLowerCase()];
  }

  private shortHostname(valueArg?: string): string | undefined {
    return valueArg?.split('.')[0];
  }
}

export class DevoloHomeNetworkManualMatcher implements IDiscoveryMatcher<IDevoloManualDiscoveryRecord> {
  public id = 'devolo-home-network-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual devolo Home Network setup entries, including snapshot-only records.';

  public async matches(inputArg: IDevoloManualDiscoveryRecord, contextArg?: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = inputArg.metadata || {};
    const host = inputArg.host || inputArg.ipAddress;
    const manufacturer = inputArg.manufacturer || inputArg.brand;
    const product = inputArg.product || inputArg.model;
    const text = [inputArg.integrationDomain, manufacturer, product, inputArg.name, inputArg.hostname, metadata.product, metadata.model]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const mtNumber = inputArg.mtNumber || this.stringValue(metadata.MT) || this.stringValue(metadata.mtNumber);
    const snapshot = inputArg.snapshot || metadata.snapshot;
    const matched = inputArg.integrationDomain === devoloHomeNetworkDomain
      || metadata.devolo === true
      || Boolean(snapshot)
      || Boolean(host && (!text || devoloTextHints.some((hintArg) => text.includes(hintArg)) || product || inputArg.serialNumber));

    if (homeControlMtNumbers.has(mtNumber || '')) {
      return { matched: false, confidence: 'high', reason: 'Manual entry is a devolo Home Control Central Unit, which is unsupported.', metadata: { homeControl: true, mtNumber } };
    }
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain devolo Home Network setup data.' };
    }

    const id = inputArg.id || inputArg.serialNumber || inputArg.macAddress || host || `snapshot-${Date.now()}`;
    return {
      matched: true,
      confidence: snapshot ? 'certain' : host && (inputArg.serialNumber || product) ? 'high' : host ? 'medium' : 'low',
      reason: snapshot ? 'Manual entry includes a devolo Home Network snapshot.' : 'Manual entry can start devolo Home Network setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: devoloHomeNetworkDomain,
        id,
        host,
        port: inputArg.port || devoloHomeNetworkDefaultPort,
        name: inputArg.name || inputArg.hostname || product || 'devolo Home Network',
        manufacturer: 'devolo',
        model: product || 'devolo Home Network device',
        serialNumber: inputArg.serialNumber,
        macAddress: DevoloHomeNetworkMapper.normalizeMac(inputArg.macAddress),
        metadata: {
          ...metadata,
          devolo: true,
          manual: true,
          product,
          mtNumber,
          snapshot,
        },
      },
      metadata: { snapshotConfigured: Boolean(snapshot), mtNumber },
    };
  }

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

export class DevoloHomeNetworkCandidateValidator implements IDiscoveryValidator {
  public id = 'devolo-home-network-candidate-validator';
  public description = 'Validate devolo Home Network discovery candidates.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg?: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const mtNumber = this.stringValue(metadata.MT) || this.stringValue(metadata.mtNumber);
    if (homeControlMtNumbers.has(mtNumber || '') || metadata.homeControl === true) {
      return { matched: false, confidence: 'high', reason: 'The candidate is a devolo Home Control Central Unit, which is unsupported.', metadata: { homeControl: true, mtNumber } };
    }
    const text = [candidateArg.integrationDomain, candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.product, metadata.Product]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const matched = candidateArg.integrationDomain === devoloHomeNetworkDomain
      || metadata.devolo === true
      || candidateArg.manufacturer?.toLowerCase() === 'devolo'
      || devoloTextHints.some((hintArg) => text.includes(hintArg))
      || metadata.snapshot !== undefined;

    return {
      matched,
      confidence: matched && candidateArg.host && (candidateArg.serialNumber || metadata.SN) ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has devolo Home Network metadata.' : 'Candidate is not a devolo Home Network device.',
      candidate: matched ? { ...candidateArg, port: candidateArg.port || devoloHomeNetworkDefaultPort } : undefined,
      normalizedDeviceId: candidateArg.serialNumber || this.stringValue(metadata.SN) || candidateArg.id || candidateArg.macAddress || candidateArg.host,
      metadata: matched ? { mtNumber, liveHttpImplemented: false } : undefined,
    };
  }

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

export const createDevoloHomeNetworkDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: devoloHomeNetworkDomain, displayName: 'devolo Home Network' })
    .addMatcher(new DevoloHomeNetworkMdnsMatcher())
    .addMatcher(new DevoloHomeNetworkManualMatcher())
    .addValidator(new DevoloHomeNetworkCandidateValidator());
};

export const createDevoloDiscoveryDescriptor = createDevoloHomeNetworkDiscoveryDescriptor;
