import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IYamahaMusiccastManualEntry, IYamahaMusiccastMdnsRecord, IYamahaMusiccastSsdpRecord } from './yamaha_musiccast.types.js';

const yamahaManufacturer = 'Yamaha Corporation';
const musiccastDomain = 'yamaha_musiccast';

export class YamahaMusiccastMdnsMatcher implements IDiscoveryMatcher<IYamahaMusiccastMdnsRecord> {
  public id = 'yamaha-musiccast-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Yamaha MusicCast mDNS records.';

  public async matches(recordArg: IYamahaMusiccastMdnsRecord): Promise<IDiscoveryMatch> {
    const txt = recordArg.txt || {};
    const model = valueForKey(txt, 'model') || valueForKey(txt, 'modelName') || valueForKey(txt, 'model_name');
    const serialNumber = valueForKey(txt, 'serial') || valueForKey(txt, 'serialNumber') || valueForKey(txt, 'serial_number');
    const systemId = valueForKey(txt, 'system_id') || valueForKey(txt, 'systemid');
    const deviceId = valueForKey(txt, 'device_id') || valueForKey(txt, 'deviceid') || valueForKey(txt, 'id');
    const manufacturer = valueForKey(txt, 'manufacturer') || valueForKey(txt, 'brand');
    const haystack = `${recordArg.name || ''} ${recordArg.type || ''} ${manufacturer || ''} ${model || ''} ${JSON.stringify(txt)}`.toLowerCase();
    const matched = haystack.includes('musiccast') || haystack.includes('yamaha') || haystack.includes('yamahaextendedcontrol');

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a Yamaha MusicCast advertisement.' };
    }

    const id = systemId || deviceId || serialNumber || recordArg.name;
    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'mDNS record matches Yamaha MusicCast metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: musiccastDomain,
        id,
        host: recordArg.host,
        port: recordArg.port || 80,
        name: recordArg.name,
        manufacturer: yamahaManufacturer,
        model,
        serialNumber,
        metadata: { mdnsName: recordArg.name, mdnsType: recordArg.type, txt, systemId, deviceId },
      },
    };
  }
}

export class YamahaMusiccastSsdpMatcher implements IDiscoveryMatcher<IYamahaMusiccastSsdpRecord> {
  public id = 'yamaha-musiccast-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Yamaha MusicCast SSDP advertisements with the YXC control URL.';

  public async matches(recordArg: IYamahaMusiccastSsdpRecord): Promise<IDiscoveryMatch> {
    const location = header(recordArg, 'location');
    const st = header(recordArg, 'st') || upnp(recordArg, 'deviceType');
    const usn = header(recordArg, 'usn');
    const manufacturer = upnp(recordArg, 'manufacturer');
    const model = upnp(recordArg, 'modelName') || upnp(recordArg, 'model');
    const serialNumber = upnp(recordArg, 'serialNumber') || upnp(recordArg, 'serial');
    const friendlyName = upnp(recordArg, 'friendlyName');
    const controlUrl = upnp(recordArg, 'X_yxcControlURL') || upnp(recordArg, 'yamaha:X_yxcControlURL');
    const haystack = `${manufacturer || ''} ${model || ''} ${friendlyName || ''} ${controlUrl || ''} ${st || ''} ${usn || ''}`.toLowerCase();
    const matched = haystack.includes('musiccast') || haystack.includes('yamahaextendedcontrol') || (isYamaha(manufacturer) && Boolean(location));

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a Yamaha MusicCast device.' };
    }

    const url = parseUrl(location);
    const id = serialNumber || stripUuid(usn);
    return {
      matched: true,
      confidence: controlUrl || serialNumber ? 'certain' : 'high',
      reason: 'SSDP record matches Yamaha MusicCast metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: musiccastDomain,
        id,
        host: url?.hostname,
        port: url?.port ? Number(url.port) : 80,
        name: friendlyName,
        manufacturer: yamahaManufacturer,
        model,
        serialNumber,
        metadata: { st, usn, location, controlUrl },
      },
    };
  }
}

export class YamahaMusiccastManualMatcher implements IDiscoveryMatcher<IYamahaMusiccastManualEntry> {
  public id = 'yamaha-musiccast-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Yamaha MusicCast setup entries.';

  public async matches(inputArg: IYamahaMusiccastManualEntry): Promise<IDiscoveryMatch> {
    const matched = Boolean(
      inputArg.host
      || isYamaha(inputArg.manufacturer)
      || includesMusiccastHint(inputArg.name)
      || includesMusiccastHint(inputArg.model)
      || inputArg.metadata?.yamaha_musiccast
      || inputArg.metadata?.musiccast
    );

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Yamaha MusicCast setup hints.' };
    }

    const id = inputArg.systemId || inputArg.deviceId || inputArg.serialNumber || inputArg.id;
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start Yamaha MusicCast setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: musiccastDomain,
        id,
        host: inputArg.host,
        port: inputArg.port || 80,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer || yamahaManufacturer,
        model: inputArg.model,
        serialNumber: inputArg.serialNumber,
        metadata: { ...inputArg.metadata, systemId: inputArg.systemId, deviceId: inputArg.deviceId },
      },
    };
  }
}

export class YamahaMusiccastCandidateValidator implements IDiscoveryValidator {
  public id = 'yamaha-musiccast-candidate-validator';
  public description = 'Validate Yamaha MusicCast candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const matched = candidateArg.integrationDomain === musiccastDomain
      || isYamaha(candidateArg.manufacturer)
      || includesMusiccastHint(candidateArg.model)
      || includesMusiccastHint(candidateArg.name)
      || Boolean(metadata.musiccast || metadata.yamaha_musiccast || metadata.systemId || metadata.deviceId);

    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Yamaha MusicCast metadata.' : 'Candidate is not a Yamaha MusicCast device.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: normalizedCandidateId(candidateArg),
      metadata: matched ? { validatedAs: musiccastDomain } : undefined,
    };
  }
}

export const createYamahaMusiccastDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: musiccastDomain, displayName: 'MusicCast' })
    .addMatcher(new YamahaMusiccastMdnsMatcher())
    .addMatcher(new YamahaMusiccastSsdpMatcher())
    .addMatcher(new YamahaMusiccastManualMatcher())
    .addValidator(new YamahaMusiccastCandidateValidator());
};

const header = (recordArg: IYamahaMusiccastSsdpRecord, keyArg: string): string | undefined => {
  return recordArg[keyArg as keyof IYamahaMusiccastSsdpRecord] as string | undefined || valueForKey(recordArg.headers, keyArg);
};

const upnp = (recordArg: IYamahaMusiccastSsdpRecord, keyArg: string): string | undefined => {
  return valueForKey(recordArg.upnp, keyArg) || valueForKey(recordArg.headers, keyArg);
};

const valueForKey = (recordArg: Record<string, string | undefined> | undefined, keyArg: string): string | undefined => {
  if (!recordArg) {
    return undefined;
  }
  const lowerKey = keyArg.toLowerCase();
  for (const [key, value] of Object.entries(recordArg)) {
    if (key.toLowerCase() === lowerKey) {
      return value;
    }
  }
  return undefined;
};

const parseUrl = (valueArg: string | undefined): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};

const stripUuid = (valueArg: string | undefined): string | undefined => {
  return valueArg?.replace(/^uuid:/i, '').split('::')[0];
};

const normalizedCandidateId = (candidateArg: IDiscoveryCandidate): string | undefined => {
  const metadata = candidateArg.metadata || {};
  return stringMetadata(metadata.systemId) || stringMetadata(metadata.deviceId) || candidateArg.serialNumber || candidateArg.id;
};

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

const isYamaha = (valueArg: string | undefined): boolean => {
  return Boolean(valueArg?.toLowerCase().includes('yamaha'));
};

const includesMusiccastHint = (valueArg: string | undefined): boolean => {
  const value = valueArg?.toLowerCase() || '';
  return value.includes('musiccast') || value.includes('yamaha');
};
