import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IDenonavrManualEntry, IDenonavrMdnsRecord, IDenonavrSsdpRecord } from './denonavr.types.js';

const supportedManufacturers = ['denon', 'denon professional', 'marantz'];
const supportedDeviceTypes = [
  'urn:schemas-upnp-org:device:mediarenderer:1',
  'urn:schemas-upnp-org:device:mediaserver:1',
  'urn:schemas-denon-com:device:aiosdevice:1',
];
const ignoredModels = ['heos 1', 'heos 3', 'heos 5', 'heos 7'];

export class DenonavrSsdpMatcher implements IDiscoveryMatcher<IDenonavrSsdpRecord> {
  public id = 'denonavr-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Denon and Marantz AVR SSDP advertisements.';

  public async matches(recordArg: IDenonavrSsdpRecord): Promise<IDiscoveryMatch> {
    const st = header(recordArg, 'st') || recordArg.upnp?.deviceType;
    const usn = header(recordArg, 'usn');
    const location = header(recordArg, 'location');
    const manufacturer = upnp(recordArg, 'manufacturer');
    const model = cleanModel(upnp(recordArg, 'modelName'));
    const serialNumber = upnp(recordArg, 'serialNumber') || upnp(recordArg, 'serial');
    const friendlyName = upnp(recordArg, 'friendlyName');
    const deviceType = upnp(recordArg, 'deviceType') || st;

    const matchedManufacturer = isSupportedManufacturer(manufacturer);
    const matchedType = Boolean(deviceType && supportedDeviceTypes.includes(deviceType.toLowerCase()));
    const matchedUsn = Boolean(usn?.toLowerCase().includes('denon') || usn?.toLowerCase().includes('marantz'));

    if (!matchedManufacturer && !matchedType && !matchedUsn) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a Denon/Marantz AVR.' };
    }
    if (isIgnoredModel(model)) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is a HEOS speaker, not an AVR.' };
    }

    const url = parseUrl(location);
    const id = uniqueId(model, serialNumber) || stripUuid(usn);
    return {
      matched: true,
      confidence: id && matchedManufacturer ? 'certain' : matchedManufacturer || matchedType ? 'high' : 'medium',
      reason: 'SSDP record matches Denon/Marantz AVR metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: 'denonavr',
        id,
        host: url?.hostname,
        port: url?.port ? Number(url.port) : undefined,
        name: friendlyName,
        manufacturer: normalizedManufacturer(manufacturer),
        model,
        serialNumber,
        metadata: { st, usn, location, deviceType },
      },
    };
  }
}

export class DenonavrMdnsMatcher implements IDiscoveryMatcher<IDenonavrMdnsRecord> {
  public id = 'denonavr-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Denon and Marantz AVR mDNS advertisements.';

  public async matches(recordArg: IDenonavrMdnsRecord): Promise<IDiscoveryMatch> {
    const name = recordArg.name || '';
    const type = recordArg.type || '';
    const manufacturer = recordArg.txt?.manufacturer || recordArg.txt?.brand || recordArg.txt?.Manufacturer;
    const model = cleanModel(recordArg.txt?.model || recordArg.txt?.modelName || recordArg.txt?.ModelName);
    const serialNumber = recordArg.txt?.serial || recordArg.txt?.serialNumber || recordArg.txt?.SerialNumber;
    const haystack = `${name} ${type} ${manufacturer || ''} ${model || ''}`.toLowerCase();
    const matched = isSupportedManufacturer(manufacturer) || haystack.includes('denon') || haystack.includes('marantz');

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a Denon/Marantz advertisement.' };
    }
    if (isIgnoredModel(model)) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is a HEOS speaker, not an AVR.' };
    }

    const id = uniqueId(model, serialNumber) || recordArg.txt?.id || name;
    return {
      matched: true,
      confidence: serialNumber ? 'certain' : 'medium',
      reason: 'mDNS record matches Denon/Marantz metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'denonavr',
        id,
        host: recordArg.host,
        port: recordArg.port,
        name,
        manufacturer: normalizedManufacturer(manufacturer),
        model,
        serialNumber,
        metadata: { mdnsName: name, mdnsType: type, txt: recordArg.txt },
      },
    };
  }
}

export class DenonavrManualMatcher implements IDiscoveryMatcher<IDenonavrManualEntry> {
  public id = 'denonavr-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Denon/Marantz AVR setup entries.';

  public async matches(inputArg: IDenonavrManualEntry): Promise<IDiscoveryMatch> {
    const model = cleanModel(inputArg.model);
    const matched = Boolean(
      inputArg.host
      || isSupportedManufacturer(inputArg.manufacturer)
      || model?.toLowerCase().includes('denon')
      || model?.toLowerCase().includes('marantz')
      || inputArg.metadata?.denonavr
    );
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Denon AVR setup hints.' };
    }
    const id = uniqueId(model, inputArg.serialNumber) || inputArg.id;
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start Denon AVR setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: 'denonavr',
        id,
        host: inputArg.host,
        port: inputArg.port || 80,
        name: inputArg.name,
        manufacturer: normalizedManufacturer(inputArg.manufacturer),
        model,
        serialNumber: inputArg.serialNumber,
        metadata: inputArg.metadata,
      },
    };
  }
}

export class DenonavrCandidateValidator implements IDiscoveryValidator {
  public id = 'denonavr-candidate-validator';
  public description = 'Validate Denon/Marantz AVR candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const model = candidateArg.model?.toLowerCase() || '';
    const matched = candidateArg.integrationDomain === 'denonavr'
      || isSupportedManufacturer(manufacturer)
      || model.includes('denon')
      || model.includes('marantz')
      || model.includes('avr')
      || model.includes('sr');
    const rejected = isIgnoredModel(model);

    return {
      matched: matched && !rejected,
      confidence: matched && !rejected && candidateArg.host ? 'high' : matched && !rejected ? 'medium' : 'low',
      reason: rejected ? 'Candidate is a HEOS speaker, not an AVR.' : matched ? 'Candidate has Denon/Marantz AVR metadata.' : 'Candidate is not a Denon AVR.',
      candidate: matched && !rejected ? candidateArg : undefined,
      normalizedDeviceId: uniqueId(candidateArg.model, candidateArg.serialNumber) || candidateArg.id,
    };
  }
}

export const createDenonavrDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'denonavr', displayName: 'Denon AVR Network Receivers' })
    .addMatcher(new DenonavrSsdpMatcher())
    .addMatcher(new DenonavrMdnsMatcher())
    .addMatcher(new DenonavrManualMatcher())
    .addValidator(new DenonavrCandidateValidator());
};

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

const upnp = (recordArg: IDenonavrSsdpRecord, 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 cleanModel = (valueArg: string | undefined): string | undefined => {
  return valueArg?.replace(/\*/g, '').trim() || undefined;
};

const isSupportedManufacturer = (valueArg: string | undefined): boolean => {
  const value = valueArg?.toLowerCase().trim();
  return Boolean(value && supportedManufacturers.includes(value));
};

const normalizedManufacturer = (valueArg: string | undefined): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  return valueArg.toLowerCase().includes('marantz') ? 'Marantz' : valueArg.toLowerCase().includes('denon') ? 'Denon' : valueArg;
};

const isIgnoredModel = (valueArg: string | undefined): boolean => {
  return Boolean(valueArg && ignoredModels.includes(valueArg.toLowerCase()));
};

const uniqueId = (modelArg: string | undefined, serialArg: string | undefined): string | undefined => {
  if (modelArg && serialArg) {
    return `${cleanModel(modelArg)}-${serialArg}`;
  }
  return serialArg;
};
