import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IHomeWizardDhcpRecord, IHomeWizardManualEntry, IHomeWizardRawData, IHomeWizardSnapshot, IHomeWizardZeroconfRecord, THomeWizardApiVersion } from './homewizard.types.js';
import { homeWizardDefaultHttpPort, homeWizardDefaultHttpsPort, homeWizardDisplayName, homeWizardDomain, homeWizardProductNames, homeWizardZeroconfTypes } from './homewizard.types.js';

export class HomeWizardZeroconfMatcher implements IDiscoveryMatcher<IHomeWizardZeroconfRecord> {
  public id = 'homewizard-zeroconf-match';
  public source = 'mdns' as const;
  public description = 'Recognize HomeWizard Energy _hwenergy._tcp.local and _homewizard._tcp.local zeroconf advertisements from the Home Assistant manifest.';

  public async matches(recordArg: IHomeWizardZeroconfRecord): Promise<IDiscoveryMatch> {
    const txt = normalizeKeys({ ...recordArg.txt, ...recordArg.properties });
    const type = normalizeMdnsType(recordArg.type || recordArg.serviceType || '');
    const isV1 = type === normalizeMdnsType(homeWizardZeroconfTypes[0]) || type.includes('_hwenergy');
    const isV2 = type === normalizeMdnsType(homeWizardZeroconfTypes[1]) || type.includes('_homewizard');
    const matched = isV1 || isV2 || recordArg.metadata?.homewizard === true;
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a HomeWizard Energy zeroconf advertisement.' };
    }

    const apiVersion: THomeWizardApiVersion = isV2 ? 'v2' : 'v1';
    const serial = stringValue(txt.serial);
    const productType = stringValue(txt.product_type);
    const productName = stringValue(txt.product_name) || productType && homeWizardProductNames[productType] || cleanServiceName(recordArg.name) || homeWizardDisplayName;
    const host = recordArg.host || recordArg.hostname || recordArg.addresses?.[0];
    const id = productType && serial ? `${productType}_${serial}` : serial || stringValue(txt.id) || cleanServiceName(recordArg.name) || host;
    return {
      matched: true,
      confidence: host && serial && productType ? 'certain' : host ? 'high' : 'medium',
      reason: `mDNS service matches ${isV2 ? homeWizardZeroconfTypes[1] : homeWizardZeroconfTypes[0]}.`,
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: homeWizardDomain,
        id,
        host,
        port: recordArg.port || (apiVersion === 'v2' ? homeWizardDefaultHttpsPort : homeWizardDefaultHttpPort),
        name: productName,
        manufacturer: homeWizardDisplayName,
        model: productType,
        serialNumber: serial,
        macAddress: normalizeMac(serial),
        metadata: {
          ...recordArg.metadata,
          homewizard: true,
          discoveryProtocol: 'zeroconf',
          mdnsType: type,
          mdnsName: recordArg.name,
          apiVersion,
          localApiEnabled: txt.api_enabled === undefined ? undefined : txt.api_enabled !== '0',
          path: txt.path,
          identifier: txt.id,
          productType,
          productName,
          serial,
          txt,
        },
      },
    };
  }
}

export class HomeWizardDhcpMatcher implements IDiscoveryMatcher<IHomeWizardDhcpRecord> {
  public id = 'homewizard-dhcp-match';
  public source = 'dhcp' as const;
  public description = 'Recognize HomeWizard registered DHCP devices and HomeWizard Energy hostnames; the Home Assistant manifest uses registered_devices DHCP updates.';

  public async matches(recordArg: IHomeWizardDhcpRecord): Promise<IDiscoveryMatch> {
    const metadata = recordArg.metadata || {};
    const hostname = stringValue(recordArg.hostname) || stringValue(recordArg.hostName) || '';
    const normalizedHostname = hostname.toLowerCase();
    const productType = stringValue(recordArg.product_type) || stringValue(recordArg.productType) || stringValue(metadata.productType);
    const serial = stringValue(recordArg.serial) || stringValue(metadata.serial) || normalizeMac(recordArg.macaddress || recordArg.macAddress)?.replace(/:/g, '');
    const hostMatches = ['p1meter-', 'energysocket-', 'kwhmeter-', 'watermeter-', 'homewizard-'].some((prefixArg) => normalizedHostname.startsWith(prefixArg));
    const matched = Boolean(recordArg.registered_devices || recordArg.registeredDevices || metadata.homewizard === true || productType || hostMatches || recordArg.metadata?.integrationDomain === homeWizardDomain);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'DHCP lease does not look like a registered HomeWizard Energy device.' };
    }
    const host = recordArg.ip || recordArg.address;
    const id = productType && serial ? `${productType}_${serial}` : serial || hostname || host;
    return {
      matched: true,
      confidence: host && id ? 'high' : 'medium',
      reason: recordArg.registered_devices || recordArg.registeredDevices ? 'DHCP lease came from registered HomeWizard devices.' : 'DHCP lease contains HomeWizard Energy hostname or metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'dhcp',
        integrationDomain: homeWizardDomain,
        id,
        host,
        port: homeWizardDefaultHttpPort,
        name: stringValue(recordArg.product_name) || stringValue(recordArg.productName) || stringValue(metadata.productName) || hostname || homeWizardDisplayName,
        manufacturer: homeWizardDisplayName,
        model: productType,
        serialNumber: serial,
        macAddress: normalizeMac(recordArg.macaddress || recordArg.macAddress || serial),
        metadata: {
          ...metadata,
          homewizard: true,
          discoveryProtocol: 'dhcp',
          apiVersion: stringValue(metadata.apiVersion) || 'auto',
          hostname,
          productType,
          serial,
        },
      },
    };
  }
}

export class HomeWizardManualMatcher implements IDiscoveryMatcher<IHomeWizardManualEntry> {
  public id = 'homewizard-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual HomeWizard host/token, snapshot, raw-data, and injected-client setup entries.';

  public async matches(inputArg: IHomeWizardManualEntry): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const snapshot = snapshotValue(inputArg.snapshot || metadata.snapshot);
    const rawData = rawDataValue(inputArg.rawData || metadata.rawData);
    const parsed = parseEndpoint(inputArg.url || inputArg.host);
    const text = [inputArg.name, inputArg.manufacturer, inputArg.model, inputArg.productName, inputArg.productType, metadata.name, metadata.productName, metadata.productType].filter(Boolean).join(' ').toLowerCase();
    const hasManualData = Boolean(snapshot || rawData || inputArg.client || metadata.client);
    const matched = Boolean(inputArg.host || inputArg.url || inputArg.token || inputArg.authorizationToken || inputArg.productType || metadata.homewizard || hasManualData || text.includes('homewizard') || text.includes('hwe-'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain HomeWizard setup hints.' };
    }
    const host = parsed?.host || (inputArg.url ? undefined : inputArg.host) || snapshot?.host;
    const apiVersion = inputArg.apiVersion || stringValue(metadata.apiVersion) as THomeWizardApiVersion | undefined || snapshot?.apiVersion || 'auto';
    const port = inputArg.port || parsed?.port || snapshot?.port || (apiVersion === 'v2' ? homeWizardDefaultHttpsPort : homeWizardDefaultHttpPort);
    const id = inputArg.id || inputArg.uniqueId || snapshot?.device.id || inputArg.serial || (host ? `${host}:${port}` : undefined);
    return {
      matched: true,
      confidence: host || hasManualData ? 'high' : 'medium',
      reason: 'Manual entry can start HomeWizard setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: homeWizardDomain,
        id,
        host,
        port,
        name: inputArg.name || snapshot?.device.productName || inputArg.productName || homeWizardDisplayName,
        manufacturer: homeWizardDisplayName,
        model: inputArg.model || inputArg.productType || snapshot?.device.productType,
        serialNumber: inputArg.serial || snapshot?.device.serial,
        metadata: {
          ...metadata,
          homewizard: true,
          discoveryProtocol: 'manual',
          apiVersion,
          token: inputArg.token || inputArg.authorizationToken || metadata.token || metadata.authorizationToken,
          productType: inputArg.productType || snapshot?.device.productType,
          productName: inputArg.productName || snapshot?.device.productName,
          serial: inputArg.serial || snapshot?.device.serial,
          protocol: inputArg.protocol,
          verifyTls: metadata.verifyTls,
          snapshot,
          rawData,
          client: inputArg.client || metadata.client,
          commandExecutor: inputArg.commandExecutor || metadata.commandExecutor,
        },
      },
    };
  }
}

export class HomeWizardCandidateValidator implements IDiscoveryValidator {
  public id = 'homewizard-candidate-validator';
  public description = 'Validate HomeWizard 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 text = [candidateArg.integrationDomain, candidateArg.name, candidateArg.manufacturer, candidateArg.model, metadata.mdnsType, metadata.productType].filter(Boolean).join(' ').toLowerCase();
    const matched = candidateArg.integrationDomain === homeWizardDomain || metadata.homewizard === true || text.includes('homewizard') || text.includes('_hwenergy') || text.includes('_homewizard') || text.includes('hwe-');
    const hasUsableSource = Boolean(candidateArg.host || snapshot || rawData || metadata.client);
    const normalizedDeviceId = candidateArg.id || snapshot?.device.id || (candidateArg.host ? `${candidateArg.host}:${candidateArg.port || homeWizardDefaultHttpPort}` : undefined);
    if (!matched || !hasUsableSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'HomeWizard candidate lacks a host, injected client, snapshot, or raw data.' : 'Candidate is not HomeWizard.',
        normalizedDeviceId,
      };
    }
    return {
      matched: true,
      confidence: candidateArg.host && normalizedDeviceId ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has HomeWizard metadata and a usable local endpoint, client, snapshot, or raw data.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        integrationDomain: homeWizardDomain,
        manufacturer: candidateArg.manufacturer || homeWizardDisplayName,
      },
    };
  }
}

export const createHomeWizardDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: homeWizardDomain, displayName: homeWizardDisplayName })
    .addMatcher(new HomeWizardZeroconfMatcher())
    .addMatcher(new HomeWizardDhcpMatcher())
    .addMatcher(new HomeWizardManualMatcher())
    .addValidator(new HomeWizardCandidateValidator());
};

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 => valueArg?.replace(/\._.*$/u, '').replace(/\.local\.?$/i, '').trim() || undefined;

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const normalizeMac = (valueArg: unknown): string | undefined => {
  const normalized = typeof valueArg === 'string' ? valueArg.replace(/[^0-9a-f]/gi, '').toLowerCase() : undefined;
  return normalized && normalized.length === 12 ? normalized.match(/.{1,2}/g)?.join(':') : undefined;
};

const snapshotValue = (valueArg: unknown): IHomeWizardSnapshot | undefined => valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'measurement' in valueArg ? valueArg as IHomeWizardSnapshot : undefined;

const rawDataValue = (valueArg: unknown): Partial<IHomeWizardRawData> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Partial<IHomeWizardRawData> : 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;
  }
};
