import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IXiaomiMiioDhcpRecord, IXiaomiMiioManualEntry, IXiaomiMiioMdnsRecord } from './xiaomi_miio.types.js';

const defaultMiioPort = 54321;
const miioMdnsType = '_miio._udp.local';
const xiaomiTextHints = ['xiaomi', 'miio', 'miot', 'mijia', 'roborock', 'rockrobo', 'zhimi', 'chuangmi', 'lumi', 'philips.light', 'viomi', 'dreame', 'deerma', 'dmaker'];

export const normalizeXiaomiMiioToken = (valueArg: unknown): string | undefined => {
  if (typeof valueArg !== 'string') {
    return undefined;
  }
  const token = valueArg.trim().toLowerCase();
  return /^[0-9a-f]{32}$/.test(token) ? token : undefined;
};

export class XiaomiMiioMdnsMatcher implements IDiscoveryMatcher<IXiaomiMiioMdnsRecord> {
  public id = 'xiaomi-miio-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Xiaomi Miio mDNS records advertised as _miio._udp.local.';

  public async matches(recordArg: IXiaomiMiioMdnsRecord): Promise<IDiscoveryMatch> {
    const type = this.normalizeType(recordArg.type || recordArg.serviceType);
    const txt = recordArg.txt || recordArg.properties || {};
    const name = recordArg.name || recordArg.hostname || '';
    const model = this.txt(txt, 'model') || this.modelFromName(name);
    const macAddress = this.formatMac(this.txt(txt, 'mac') || this.macFromPoch(this.txt(txt, 'poch')));
    const did = this.txt(txt, 'did') || this.txt(txt, 'id');
    const matched = type === miioMdnsType || Boolean(model && name.toLowerCase().includes('miio'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a Xiaomi Miio advertisement.' };
    }
    const host = recordArg.host || recordArg.hostname || recordArg.addresses?.[0];
    const id = macAddress || did || host || name;
    return {
      matched: true,
      confidence: host && (macAddress || model) ? 'certain' : host ? 'high' : 'medium',
      reason: 'mDNS record matches Xiaomi Miio zeroconf metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'xiaomi_miio',
        id,
        host,
        port: recordArg.port || defaultMiioPort,
        name: model ? `${model} ${host || ''}`.trim() : name,
        manufacturer: 'Xiaomi',
        model,
        macAddress,
        metadata: {
          xiaomiMiio: true,
          mdnsName: recordArg.name,
          mdnsType: recordArg.type || recordArg.serviceType,
          txt,
          did,
          model,
          macAddress,
        },
      },
      metadata: { model, macAddress, did },
    };
  }

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

  private normalizeType(valueArg?: string): string {
    return (valueArg || '').toLowerCase().replace(/\.$/, '');
  }

  private macFromPoch(valueArg?: string): string | undefined {
    const match = /mac=([0-9a-f:]+)/i.exec(valueArg || '');
    return match?.[1];
  }

  private modelFromName(valueArg: string): string | undefined {
    const raw = valueArg.split('._')[0].replace(/_miio.*$/i, '').replace(/\.local\.?$/i, '').trim();
    if (!raw || raw.startsWith('_')) {
      return undefined;
    }
    return raw.replace(/-/g, '.');
  }

  private formatMac(valueArg?: string): string | undefined {
    const compact = (valueArg || '').replace(/[^0-9a-f]/gi, '').toLowerCase();
    if (compact.length !== 12) {
      return undefined;
    }
    return compact.match(/.{1,2}/g)?.join(':');
  }
}

export class XiaomiMiioDhcpMatcher implements IDiscoveryMatcher<IXiaomiMiioDhcpRecord> {
  public id = 'xiaomi-miio-dhcp-match';
  public source = 'dhcp' as const;
  public description = 'Recognize Xiaomi Miio devices from DHCP hostname, manufacturer, model, or metadata hints.';

  public async matches(recordArg: IXiaomiMiioDhcpRecord): Promise<IDiscoveryMatch> {
    const host = recordArg.host || recordArg.ipAddress || recordArg.address;
    const hostname = recordArg.hostname || recordArg.hostName;
    const metadata = recordArg.metadata || {};
    const text = [hostname, recordArg.manufacturer, recordArg.model, recordArg.vendorClassIdentifier, metadata.model, metadata.manufacturer]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const token = normalizeXiaomiMiioToken(metadata.token);
    const matched = Boolean(recordArg.integrationDomain === 'xiaomi_miio' || metadata.xiaomiMiio || token || xiaomiTextHints.some((hintArg) => text.includes(hintArg)));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'DHCP record does not contain Xiaomi Miio metadata.' };
    }
    const macAddress = this.formatMac(recordArg.macAddress || recordArg.mac || this.stringValue(metadata.macAddress));
    const id = macAddress || recordArg.deviceId as string | undefined || hostname || host;
    return {
      matched: true,
      confidence: host && token ? 'certain' : host && macAddress ? 'high' : host ? 'medium' : 'low',
      reason: 'DHCP record contains Xiaomi Miio host or vendor metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'dhcp',
        integrationDomain: 'xiaomi_miio',
        id,
        host,
        port: defaultMiioPort,
        name: hostname || recordArg.model || 'Xiaomi Miio device',
        manufacturer: recordArg.manufacturer || 'Xiaomi',
        model: recordArg.model || this.stringValue(metadata.model),
        macAddress,
        metadata: { ...metadata, xiaomiMiio: true, token, macAddress },
      },
      metadata: { tokenConfigured: Boolean(token), macAddress },
    };
  }

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

  private formatMac(valueArg?: string): string | undefined {
    const compact = (valueArg || '').replace(/[^0-9a-f]/gi, '').toLowerCase();
    if (compact.length !== 12) {
      return undefined;
    }
    return compact.match(/.{1,2}/g)?.join(':');
  }
}

export class XiaomiMiioManualMatcher implements IDiscoveryMatcher<IXiaomiMiioManualEntry> {
  public id = 'xiaomi-miio-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Xiaomi Miio host and token setup entries.';

  public async matches(inputArg: IXiaomiMiioManualEntry): Promise<IDiscoveryMatch> {
    const host = inputArg.host;
    const token = normalizeXiaomiMiioToken(inputArg.token || inputArg.metadata?.token);
    if (!host || !token) {
      return { matched: false, confidence: 'low', reason: 'Manual Xiaomi Miio setup requires host and a 32-character hexadecimal token.' };
    }
    const id = inputArg.deviceId || inputArg.id || inputArg.macAddress || host;
    return {
      matched: true,
      confidence: inputArg.model ? 'certain' : 'high',
      reason: 'Manual entry contains Xiaomi Miio host and token.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: 'xiaomi_miio',
        id,
        host,
        port: inputArg.port || defaultMiioPort,
        name: inputArg.name || inputArg.model || 'Xiaomi Miio device',
        manufacturer: 'Xiaomi',
        model: inputArg.model,
        macAddress: inputArg.macAddress,
        metadata: { ...inputArg.metadata, xiaomiMiio: true, token, snapshot: inputArg.snapshot },
      },
      metadata: { tokenConfigured: true },
    };
  }
}

export class XiaomiMiioCandidateValidator implements IDiscoveryValidator {
  public id = 'xiaomi-miio-candidate-validator';
  public description = 'Validate Xiaomi Miio discovery candidates and manual token shape.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const token = normalizeXiaomiMiioToken(metadata.token);
    if (candidateArg.source === 'manual' && (!candidateArg.host || !token)) {
      return { matched: false, confidence: 'low', reason: 'Manual Xiaomi Miio candidates require host and a valid 32-character hexadecimal token.' };
    }
    if (metadata.token !== undefined && !token) {
      return { matched: false, confidence: 'low', reason: 'Xiaomi Miio token metadata is present but invalid.' };
    }
    const text = [candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.model, metadata.manufacturer]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const matched = Boolean(candidateArg.integrationDomain === 'xiaomi_miio' || metadata.xiaomiMiio || token || xiaomiTextHints.some((hintArg) => text.includes(hintArg)));
    return {
      matched,
      confidence: matched && candidateArg.host && token ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Xiaomi Miio metadata.' : 'Candidate is not Xiaomi Miio.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.macAddress || candidateArg.id || candidateArg.host,
      metadata: matched ? { tokenConfigured: Boolean(token), host: candidateArg.host } : undefined,
    };
  }
}

export const createXiaomiMiioDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'xiaomi_miio', displayName: 'Xiaomi Home' })
    .addMatcher(new XiaomiMiioMdnsMatcher())
    .addMatcher(new XiaomiMiioDhcpMatcher())
    .addMatcher(new XiaomiMiioManualMatcher())
    .addValidator(new XiaomiMiioCandidateValidator());
};
