import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { HunterDouglasPowerViewMapper } from './hunterdouglas_powerview.mapper.js';
import type { IHunterDouglasPowerViewDhcpRecord, IHunterDouglasPowerViewManualEntry, IHunterDouglasPowerViewRawData, IHunterDouglasPowerViewSnapshot, IHunterDouglasPowerViewZeroconfRecord } from './hunterdouglas_powerview.types.js';
import { hunterDouglasPowerViewDefaultPort, hunterDouglasPowerViewDhcpHostnamePrefix, hunterDouglasPowerViewDhcpMacPrefix, hunterDouglasPowerViewDisplayName, hunterDouglasPowerViewDomain, hunterDouglasPowerViewManufacturer, hunterDouglasPowerViewZeroconfTypes } from './hunterdouglas_powerview.types.js';

export class HunterDouglasPowerViewZeroconfMatcher implements IDiscoveryMatcher<IHunterDouglasPowerViewZeroconfRecord> {
  public id = 'hunterdouglas_powerview-zeroconf-match';
  public source = 'mdns' as const;
  public description = 'Recognize PowerView _powerview._tcp.local and _PowerView-G3._tcp.local zeroconf advertisements from the Home Assistant manifest.';

  public async matches(recordArg: IHunterDouglasPowerViewZeroconfRecord): Promise<IDiscoveryMatch> {
    const txt = normalizeKeys({ ...recordArg.txt, ...recordArg.properties });
    const type = normalizeMdnsType(recordArg.type || recordArg.serviceType || '');
    const knownTypes = hunterDouglasPowerViewZeroconfTypes.map((typeArg) => normalizeMdnsType(typeArg));
    const matched = knownTypes.includes(type) || type.includes('_powerview') || type.includes('_powerview-g3') || recordArg.metadata?.hunterDouglasPowerView === true;
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a PowerView advertisement.' };
    }
    const host = recordArg.host || recordArg.hostname || recordArg.addresses?.[0];
    const name = cleanServiceName(recordArg.name) || stringValue(txt.name) || hunterDouglasPowerViewDisplayName;
    const id = stringValue(txt.serialnumber) || stringValue(txt.serial) || stringValue(txt.id) || cleanServiceName(recordArg.name) || host;
    return {
      matched: true,
      confidence: host && id ? 'certain' : host ? 'high' : 'medium',
      reason: 'mDNS service matches PowerView zeroconf service type.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: hunterDouglasPowerViewDomain,
        id,
        host,
        port: recordArg.port || hunterDouglasPowerViewDefaultPort,
        name,
        manufacturer: hunterDouglasPowerViewManufacturer,
        model: type.includes('g3') ? 'Powerview Generation 3' : 'PowerView Hub',
        serialNumber: stringValue(txt.serialnumber) || stringValue(txt.serial),
        metadata: {
          ...recordArg.metadata,
          hunterDouglasPowerView: true,
          discoveryProtocol: 'zeroconf',
          mdnsType: type,
          mdnsName: recordArg.name,
          apiVersion: type.includes('g3') ? 3 : undefined,
          txt,
        },
      },
    };
  }
}

export class HunterDouglasPowerViewDhcpMatcher implements IDiscoveryMatcher<IHunterDouglasPowerViewDhcpRecord> {
  public id = 'hunterdouglas_powerview-dhcp-match';
  public source = 'dhcp' as const;
  public description = 'Recognize PowerView DHCP leases from registered devices or hunter* hostnames with 002674* MAC addresses from the Home Assistant manifest.';

  public async matches(recordArg: IHunterDouglasPowerViewDhcpRecord): Promise<IDiscoveryMatch> {
    const hostname = stringValue(recordArg.hostname) || stringValue(recordArg.hostName) || '';
    const normalizedHostname = hostname.toLowerCase();
    const normalizedMac = normalizeMacPrefix(recordArg.macaddress || recordArg.macAddress);
    const registeredDevice = Boolean(recordArg.registeredDevices || recordArg.registered_devices || recordArg.metadata?.registeredDevices || recordArg.metadata?.registered_devices);
    const explicitMatch = normalizedHostname.startsWith(hunterDouglasPowerViewDhcpHostnamePrefix) && Boolean(normalizedMac?.startsWith(hunterDouglasPowerViewDhcpMacPrefix));
    const metadataMatch = recordArg.metadata?.hunterDouglasPowerView === true;
    const text = [recordArg.manufacturer, recordArg.model, hostname, recordArg.metadata?.manufacturer, recordArg.metadata?.model].filter(Boolean).join(' ').toLowerCase();
    const registeredPowerView = registeredDevice && (text.includes('powerview') || text.includes('hunter') || text.includes('douglas'));
    const matched = explicitMatch || metadataMatch || registeredPowerView;
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'DHCP lease does not match PowerView registered-device, hostname, or MAC patterns.' };
    }
    const host = recordArg.ip || recordArg.address;
    const id = normalizedMac || hostname || host;
    return {
      matched: true,
      confidence: host && explicitMatch ? 'certain' : host ? 'high' : 'medium',
      reason: explicitMatch ? 'DHCP lease matches hunter* hostname and 002674* MAC prefix.' : 'DHCP lease matches a registered PowerView device hint.',
      normalizedDeviceId: id,
      candidate: {
        source: 'dhcp',
        integrationDomain: hunterDouglasPowerViewDomain,
        id,
        host,
        port: hunterDouglasPowerViewDefaultPort,
        name: hostname || hunterDouglasPowerViewDisplayName,
        manufacturer: hunterDouglasPowerViewManufacturer,
        macAddress: HunterDouglasPowerViewMapper.normalizeMac(normalizedMac),
        model: 'PowerView Hub',
        metadata: {
          ...recordArg.metadata,
          hunterDouglasPowerView: true,
          discoveryProtocol: 'dhcp',
          hostname,
          registeredDevice,
          macAddress: HunterDouglasPowerViewMapper.normalizeMac(normalizedMac),
        },
      },
    };
  }
}

export class HunterDouglasPowerViewManualMatcher implements IDiscoveryMatcher<IHunterDouglasPowerViewManualEntry> {
  public id = 'hunterdouglas_powerview-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual PowerView host, API version, snapshot, injected client, and raw-data setup entries.';

  public async matches(inputArg: IHunterDouglasPowerViewManualEntry): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const parsed = parseEndpoint(inputArg.url || inputArg.host);
    const snapshot = snapshotValue(inputArg.snapshot || metadata.snapshot);
    const rawData = rawDataValue(inputArg.rawData || metadata.rawData);
    const hasManualData = Boolean(snapshot || rawData || inputArg.client || metadata.client);
    const text = [inputArg.name, inputArg.manufacturer, inputArg.model, metadata.name, metadata.manufacturer, metadata.model].filter(Boolean).join(' ').toLowerCase();
    const matched = Boolean(inputArg.host || inputArg.url || inputArg.apiVersion || metadata.hunterDouglasPowerView || hasManualData || text.includes('powerview') || text.includes('hunter douglas'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain PowerView setup hints.' };
    }
    const host = parsed?.host || (inputArg.url ? undefined : inputArg.host) || snapshot?.hub.host;
    const port = inputArg.port || parsed?.port || snapshot?.hub.port || hunterDouglasPowerViewDefaultPort;
    const id = inputArg.id || inputArg.uniqueId || inputArg.serialNumber || snapshot?.hub.id || (host ? `${host}:${port}` : undefined);
    return {
      matched: true,
      confidence: host || hasManualData ? 'high' : 'medium',
      reason: 'Manual entry can start PowerView setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: hunterDouglasPowerViewDomain,
        id,
        host,
        port,
        name: inputArg.name || snapshot?.hub.name || hunterDouglasPowerViewDisplayName,
        manufacturer: inputArg.manufacturer || snapshot?.hub.manufacturer || hunterDouglasPowerViewManufacturer,
        model: inputArg.model || snapshot?.hub.model,
        serialNumber: inputArg.serialNumber || snapshot?.hub.serialNumber,
        macAddress: inputArg.macAddress || snapshot?.hub.macAddress,
        metadata: {
          ...metadata,
          hunterDouglasPowerView: true,
          discoveryProtocol: 'manual',
          apiVersion: inputArg.apiVersion || metadata.apiVersion || snapshot?.hub.apiVersion,
          snapshot,
          rawData,
          client: inputArg.client || metadata.client,
          commandExecutor: inputArg.commandExecutor || metadata.commandExecutor,
        },
      },
    };
  }
}

export class HunterDouglasPowerViewCandidateValidator implements IDiscoveryValidator {
  public id = 'hunterdouglas_powerview-candidate-validator';
  public description = 'Validate PowerView candidates from zeroconf, DHCP, and manual setup.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const snapshot = snapshotValue(metadata.snapshot);
    const rawData = rawDataValue(metadata.rawData);
    const mdnsType = typeof metadata.mdnsType === 'string' ? metadata.mdnsType.toLowerCase() : '';
    const discoveryProtocol = typeof metadata.discoveryProtocol === 'string' ? metadata.discoveryProtocol : undefined;
    const text = [candidateArg.integrationDomain, candidateArg.name, candidateArg.manufacturer, candidateArg.model, mdnsType].filter(Boolean).join(' ').toLowerCase();
    const matched = candidateArg.integrationDomain === hunterDouglasPowerViewDomain
      || metadata.hunterDouglasPowerView === true
      || text.includes('powerview')
      || text.includes('hunter douglas')
      || mdnsType.includes('_powerview')
      || discoveryProtocol === 'dhcp';
    const hasUsableSource = Boolean(candidateArg.host || snapshot || rawData || metadata.client);
    const normalizedDeviceId = candidateArg.serialNumber || candidateArg.id || snapshot?.hub.id || (candidateArg.host ? `${candidateArg.host}:${candidateArg.port || hunterDouglasPowerViewDefaultPort}` : undefined);
    if (!matched || !hasUsableSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'PowerView candidate lacks a host, injected client, snapshot, or manual raw data.' : 'Candidate is not PowerView.',
        normalizedDeviceId,
      };
    }
    return {
      matched: true,
      confidence: candidateArg.host && normalizedDeviceId ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has PowerView metadata and a usable local endpoint, client, snapshot, or manual raw data.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        integrationDomain: hunterDouglasPowerViewDomain,
        port: candidateArg.port || hunterDouglasPowerViewDefaultPort,
        manufacturer: candidateArg.manufacturer || hunterDouglasPowerViewManufacturer,
      },
    };
  }
}

export const createHunterDouglasPowerViewDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: hunterDouglasPowerViewDomain, displayName: hunterDouglasPowerViewDisplayName })
    .addMatcher(new HunterDouglasPowerViewZeroconfMatcher())
    .addMatcher(new HunterDouglasPowerViewDhcpMatcher())
    .addMatcher(new HunterDouglasPowerViewManualMatcher())
    .addValidator(new HunterDouglasPowerViewCandidateValidator());
};

const normalizeMdnsType = (valueArg: string): string => valueArg.toLowerCase().replace(/\.$/, '');
const normalizeKeys = (recordArg: Record<string, string | undefined>): Record<string, string | undefined> => {
  const normalized: Record<string, string | undefined> = {};
  for (const [key, value] of Object.entries(recordArg)) {
    normalized[key.toLowerCase()] = value;
  }
  return normalized;
};
const cleanServiceName = (valueArg: string | undefined): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  return valueArg.replace(/\._.*$/u, '').replace(/\.local\.?$/i, '').trim() || undefined;
};
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const normalizeMacPrefix = (valueArg: unknown): string | undefined => typeof valueArg === 'string' ? valueArg.replace(/[^0-9a-f]/gi, '').toUpperCase() : undefined;
const parseEndpoint = (valueArg: string | undefined): { host: string; port?: number } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `http://${valueArg}`);
    return { host: url.hostname, port: url.port ? Number(url.port) : undefined };
  } catch {
    return undefined;
  }
};
const snapshotValue = (valueArg: unknown): IHunterDouglasPowerViewSnapshot | undefined => valueArg && typeof valueArg === 'object' && 'hub' in valueArg && 'shades' in valueArg ? valueArg as IHunterDouglasPowerViewSnapshot : undefined;
const rawDataValue = (valueArg: unknown): Partial<IHunterDouglasPowerViewRawData> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Partial<IHunterDouglasPowerViewRawData> : undefined;
