import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { ISoundtouchManualEntry, ISoundtouchMdnsRecord } from './soundtouch.types.js';
import { soundtouchDefaultPort } from './soundtouch.types.js';

const soundtouchDomain = 'soundtouch';
const soundtouchMdnsType = '_soundtouch._tcp.local.';

export class SoundtouchMdnsMatcher implements IDiscoveryMatcher<ISoundtouchMdnsRecord> {
  public id = 'soundtouch-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Bose SoundTouch zeroconf advertisements.';

  public async matches(recordArg: ISoundtouchMdnsRecord, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const type = recordArg.type || '';
    const txt = recordArg.txt || {};
    const matched = normalizeMdnsType(type) === normalizeMdnsType(soundtouchMdnsType);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a SoundTouch advertisement.' };
    }

    const id = valueForKey(txt, 'deviceId') || valueForKey(txt, 'deviceID') || valueForKey(txt, 'id') || recordArg.name;
    return {
      matched: true,
      confidence: recordArg.host ? 'certain' : 'high',
      reason: 'mDNS record matches Bose SoundTouch zeroconf metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: soundtouchDomain,
        id,
        host: recordArg.host,
        port: recordArg.port || soundtouchDefaultPort,
        name: recordArg.name,
        manufacturer: 'Bose Corporation',
        model: valueForKey(txt, 'type') || valueForKey(txt, 'model'),
        macAddress: valueForKey(txt, 'mac') || valueForKey(txt, 'macAddress'),
        metadata: { mdnsName: recordArg.name, mdnsType: type, txt },
      },
    };
  }
}

export class SoundtouchManualMatcher implements IDiscoveryMatcher<ISoundtouchManualEntry> {
  public id = 'soundtouch-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Bose SoundTouch setup entries.';

  public async matches(inputArg: ISoundtouchManualEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const haystack = `${inputArg.name || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
    const matched = Boolean(inputArg.host || inputArg.metadata?.soundtouch || haystack.includes('soundtouch') || haystack.includes('bose'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain SoundTouch setup hints.' };
    }

    const id = inputArg.deviceId || inputArg.macAddress || inputArg.id || (inputArg.host ? `${inputArg.host}:${inputArg.port || soundtouchDefaultPort}` : undefined);
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start SoundTouch setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: soundtouchDomain,
        id,
        host: inputArg.host,
        port: inputArg.port || soundtouchDefaultPort,
        name: inputArg.name,
        manufacturer: normalizedManufacturer(inputArg.manufacturer),
        model: inputArg.model,
        macAddress: inputArg.macAddress,
        metadata: inputArg.metadata,
      },
    };
  }
}

export class SoundtouchCandidateValidator implements IDiscoveryValidator {
  public id = 'soundtouch-candidate-validator';
  public description = 'Validate Bose SoundTouch candidates.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const matched = candidateArg.integrationDomain === soundtouchDomain
      || includesSoundtouch(candidateArg.name)
      || includesSoundtouch(candidateArg.model)
      || includesBose(candidateArg.manufacturer)
      || Boolean(metadata.soundtouch)
      || normalizeMdnsType(stringMetadata(metadata.mdnsType) || '') === normalizeMdnsType(soundtouchMdnsType);

    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Bose SoundTouch metadata.' : 'Candidate is not SoundTouch.',
      candidate: matched ? { ...candidateArg, port: candidateArg.port || soundtouchDefaultPort } : undefined,
      normalizedDeviceId: candidateArg.id || candidateArg.macAddress,
    };
  }
}

export const createSoundtouchDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: soundtouchDomain, displayName: 'Bose SoundTouch' })
    .addMatcher(new SoundtouchMdnsMatcher())
    .addMatcher(new SoundtouchManualMatcher())
    .addValidator(new SoundtouchCandidateValidator());
};

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 normalizeMdnsType = (valueArg: string): string => valueArg.toLowerCase().replace(/\.$/, '');

const normalizedManufacturer = (valueArg: string | undefined): string => {
  if (!valueArg || valueArg.toLowerCase().includes('bose')) {
    return 'Bose Corporation';
  }
  return valueArg;
};

const includesSoundtouch = (valueArg: string | undefined): boolean => Boolean(valueArg?.toLowerCase().includes('soundtouch'));

const includesBose = (valueArg: string | undefined): boolean => Boolean(valueArg?.toLowerCase().includes('bose'));

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