import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type {
  IDiscoveryCandidate,
  IDiscoveryContext,
  IDiscoveryMatch,
  IDiscoveryMatcher,
  IDiscoveryProbe,
  IDiscoveryProbeResult,
  IDiscoveryValidator,
} from '../../core/types.js';
import { HuaweiLteMapper } from './huawei_lte.mapper.js';
import type { IHuaweiLteManualEntry, IHuaweiLteRawData, IHuaweiLteSnapshot, IHuaweiLteSsdpRecord } from './huawei_lte.types.js';
import {
  huaweiLteDefaultDeviceName,
  huaweiLteDefaultHttpPort,
  huaweiLteDefaultManufacturer,
  huaweiLteDisplayName,
  huaweiLteDomain,
  huaweiLteSsdpDeviceType,
  huaweiLteSsdpManufacturers,
} from './huawei_lte.types.js';

export class HuaweiLteSsdpDiscoveryProbe implements IDiscoveryProbe {
  public id = 'huawei_lte-ssdp-discovery-probe';
  public source = 'ssdp' as const;
  public description = 'Discover Huawei LTE routers using local SSDP InternetGatewayDevice M-SEARCH responses.';

  public async probe(contextArg: IDiscoveryContext): Promise<IDiscoveryProbeResult> {
    if (contextArg.abortSignal?.aborted) {
      return { candidates: [] };
    }
    return { candidates: await this.discover(1500) };
  }

  private async discover(timeoutMsArg: number): Promise<IDiscoveryCandidate[]> {
    const { createSocket } = await import('node:dgram');
    const matcher = new HuaweiLteSsdpMatcher();
    const candidates: IDiscoveryCandidate[] = [];
    const message = Buffer.from([
      'M-SEARCH * HTTP/1.1',
      'HOST: 239.255.255.250:1900',
      'MAN: "ssdp:discover"',
      'MX: 1',
      `ST: ${huaweiLteSsdpDeviceType}`,
      '',
      '',
    ].join('\r\n'), 'ascii');

    return new Promise((resolve) => {
      const socket = createSocket({ type: 'udp4', reuseAddr: true });
      const timer = setTimeout(() => {
        closeSocket();
        resolve(candidates);
      }, timeoutMsArg);

      const closeSocket = () => {
        clearTimeout(timer);
        try {
          socket.close();
        } catch {
          // Discovery sockets may already be closed after timeout or OS errors.
        }
      };

      socket.on('message', async (dataArg, remoteArg) => {
        const record = parseSsdpResponse(dataArg.toString('utf8'), remoteArg.address);
        if (!record) {
          return;
        }
        const match = await matcher.matches(record);
        if (match.matched && match.candidate && !candidates.some((candidateArg) => candidateArg.id === match.candidate?.id || candidateArg.host === match.candidate?.host)) {
          candidates.push(match.candidate);
        }
      });
      socket.on('error', () => {
        closeSocket();
        resolve(candidates);
      });
      socket.bind(() => {
        socket.setMulticastTTL(2);
        socket.send(message, 1900, '239.255.255.250');
      });
    });
  }
}

export class HuaweiLteSsdpMatcher implements IDiscoveryMatcher<IHuaweiLteSsdpRecord> {
  public id = 'huawei_lte-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Huawei LTE SSDP records matching the Home Assistant manifest InternetGatewayDevice metadata.';

  public async matches(recordArg: IHuaweiLteSsdpRecord): Promise<IDiscoveryMatch> {
    const headers = normalizeKeys({ ...recordArg.headers, ...recordArg.ssdpHeaders });
    const upnp = normalizeKeys(recordArg.upnp || {});
    const metadata = recordArg.metadata || {};
    const st = stringValue(recordArg.st || headers.st);
    const nt = stringValue(recordArg.nt || headers.nt);
    const usn = stringValue(recordArg.usn || headers.usn);
    const location = stringValue(recordArg.location || recordArg.ssdpLocation || headers.location || metadata.location || metadata.ssdpLocation);
    const presentationUrl = stringValue(recordArg.presentationUrl || upnp.presentationurl || metadata.url || metadata.presentationUrl) || location;
    const parsed = parseUrl(presentationUrl) || parseUrl(location);
    const deviceType = stringValue(recordArg.deviceType || upnp.devicetype || metadata.deviceType || st || nt);
    const manufacturer = stringValue(recordArg.manufacturer || upnp.manufacturer || metadata.manufacturer);
    const friendlyName = stringValue(recordArg.friendlyName || upnp.friendlyname || metadata.friendlyName || recordArg.name);
    const model = stringValue(recordArg.modelName || upnp.modelname || metadata.modelName);
    const serial = stringValue(recordArg.serialNumber || upnp.serialnumber || upnp.serial || metadata.serialNumber);
    const udn = stripUuid(stringValue(recordArg.udn || upnp.udn || upnp.UDN || usn?.split('::')[0] || metadata.udn || metadata.upnpUdn));
    const server = stringValue(headers.server);
    const matchedDeviceType = normalizeUrn(deviceType) === normalizeUrn(huaweiLteSsdpDeviceType) || normalizeUrn(usn).includes(normalizeUrn(huaweiLteSsdpDeviceType));
    const matchedManufacturer = isKnownManufacturer(manufacturer) || isKnownManufacturer(server);
    const explicitHint = metadata.huaweiLte === true || metadata.huawei_lte === true;

    if ((!matchedDeviceType || !matchedManufacturer) && !explicitHint) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a Huawei LTE InternetGatewayDevice advertisement.' };
    }

    const host = stringValue(recordArg.host) || parsed?.hostname;
    const port = urlPort(parsed) || huaweiLteDefaultHttpPort;
    const id = serial || udn || host;
    return {
      matched: true,
      confidence: matchedDeviceType && matchedManufacturer && host && id ? 'certain' : host ? 'high' : 'medium',
      reason: matchedDeviceType && matchedManufacturer ? 'SSDP record matches Home Assistant Huawei LTE manifest metadata.' : 'SSDP record has explicit Huawei LTE metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: huaweiLteDomain,
        id,
        host,
        port,
        name: friendlyName || model || huaweiLteDisplayName,
        manufacturer: manufacturer || huaweiLteDefaultManufacturer,
        model,
        serialNumber: serial,
        metadata: {
          ...metadata,
          huaweiLte: true,
          ssdpDeviceType: deviceType,
          ssdpLocation: location,
          ssdpManufacturer: manufacturer,
          ssdpUsn: usn,
          upnpUdn: udn,
          url: presentationUrl ? normalizeEndpointUrl(presentationUrl) : host ? `http://${hostForUrl(host)}/` : undefined,
          verifySsl: false,
          upnp: recordArg.upnp,
        },
      },
    };
  }
}

export class HuaweiLteManualMatcher implements IDiscoveryMatcher<IHuaweiLteManualEntry> {
  public id = 'huawei_lte-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Huawei LTE URL, host, snapshot, raw-data, or injected client setup entries.';

  public async matches(inputArg: IHuaweiLteManualEntry): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const parsed = parseUrl(stringValue(inputArg.url || inputArg.host || metadata.url));
    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.url || inputArg.host || inputArg.username || inputArg.password || metadata.huaweiLte || metadata.huawei_lte || hasManualData || text.includes('huawei'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Huawei LTE setup hints.' };
    }

    const host = parsed?.hostname || (inputArg.url ? undefined : inputArg.host) || snapshot?.device.host;
    const port = inputArg.port || urlPort(parsed) || snapshot?.device.port || (parsed?.protocol === 'https:' ? 443 : huaweiLteDefaultHttpPort);
    const macAddress = inputArg.macAddress || inputArg.macAddresses?.[0] || snapshot?.device.macAddresses[0];
    const id = inputArg.id || inputArg.uniqueId || inputArg.serialNumber || snapshot?.device.id || snapshot?.device.serialNumber || macAddress || (host ? `${host}:${port}` : undefined);
    return {
      matched: true,
      confidence: host || hasManualData ? 'high' : 'medium',
      reason: 'Manual entry can start Huawei LTE setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: huaweiLteDomain,
        id,
        host,
        port,
        name: inputArg.name || snapshot?.device.name || huaweiLteDefaultDeviceName,
        manufacturer: inputArg.manufacturer || snapshot?.device.manufacturer || huaweiLteDefaultManufacturer,
        model: inputArg.model || snapshot?.device.model,
        serialNumber: inputArg.serialNumber || snapshot?.device.serialNumber,
        macAddress: HuaweiLteMapper.normalizeMac(macAddress),
        metadata: {
          ...metadata,
          huaweiLte: true,
          discoveryProtocol: 'manual',
          url: normalizeEndpointUrl(inputArg.url || stringValue(metadata.url)) || (host ? `${parsed?.protocol || 'http:'}//${hostForUrl(host)}${port && port !== huaweiLteDefaultHttpPort ? `:${port}` : ''}/` : undefined),
          verifySsl: inputArg.verifySsl ?? booleanMetadata(metadata, 'verifySsl') ?? false,
          username: inputArg.username || stringValue(metadata.username),
          password: inputArg.password || stringValue(metadata.password),
          unauthenticatedMode: inputArg.unauthenticatedMode ?? booleanMetadata(metadata, 'unauthenticatedMode'),
          trackWiredClients: inputArg.trackWiredClients ?? booleanMetadata(metadata, 'trackWiredClients'),
          recipients: inputArg.recipients || stringList(metadata.recipients),
          snapshot,
          rawData,
          client: inputArg.client || metadata.client,
          commandExecutor: inputArg.commandExecutor || metadata.commandExecutor,
        },
      },
    };
  }
}

export class HuaweiLteCandidateValidator implements IDiscoveryValidator {
  public id = 'huawei_lte-candidate-validator';
  public description = 'Validate Huawei LTE candidates have a local URL, host, snapshot, raw data, or injected client.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const text = [candidateArg.integrationDomain, candidateArg.name, candidateArg.manufacturer, candidateArg.model, metadata.manufacturer, metadata.ssdpManufacturer, metadata.ssdpDeviceType].filter(Boolean).join(' ').toLowerCase();
    const snapshot = snapshotValue(metadata.snapshot);
    const rawData = rawDataValue(metadata.rawData);
    const matched = candidateArg.integrationDomain === huaweiLteDomain
      || metadata.huaweiLte === true
      || metadata.huawei_lte === true
      || text.includes('huawei')
      || text.includes('soyea')
      || normalizeUrn(stringValue(metadata.ssdpDeviceType)) === normalizeUrn(huaweiLteSsdpDeviceType);
    const hasUsableSource = Boolean(candidateArg.host || metadata.url || snapshot || rawData || metadata.client);
    const normalizedDeviceId = candidateArg.serialNumber || candidateArg.id || stringValue(metadata.upnpUdn) || snapshot?.device.serialNumber || snapshot?.device.macAddresses[0] || (candidateArg.host ? `${candidateArg.host}:${candidateArg.port || huaweiLteDefaultHttpPort}` : undefined);
    if (!matched || !hasUsableSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'Huawei LTE candidate lacks a host, URL, injected client, snapshot, or raw data.' : 'Candidate is not Huawei LTE.',
        normalizedDeviceId,
      };
    }

    return {
      matched: true,
      confidence: candidateArg.host && normalizedDeviceId ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has Huawei LTE metadata and a usable local endpoint, client, snapshot, or raw data.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        integrationDomain: huaweiLteDomain,
        port: candidateArg.port || huaweiLteDefaultHttpPort,
        manufacturer: candidateArg.manufacturer || huaweiLteDefaultManufacturer,
        metadata: {
          ...metadata,
          huaweiLte: true,
        },
      },
    };
  }
}

export const createHuaweiLteDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: huaweiLteDomain, displayName: huaweiLteDisplayName })
    .addProbe(new HuaweiLteSsdpDiscoveryProbe())
    .addMatcher(new HuaweiLteSsdpMatcher())
    .addMatcher(new HuaweiLteManualMatcher())
    .addValidator(new HuaweiLteCandidateValidator());
};

const parseSsdpResponse = (valueArg: string, remoteAddressArg: string): IHuaweiLteSsdpRecord | undefined => {
  const lines = valueArg.split(/\r?\n/g).map((lineArg) => lineArg.trim()).filter(Boolean);
  if (!lines.length || !/^HTTP\//i.test(lines[0])) {
    return undefined;
  }
  const headers: Record<string, string> = {};
  for (const line of lines.slice(1)) {
    const index = line.indexOf(':');
    if (index === -1) {
      continue;
    }
    headers[line.slice(0, index).toLowerCase()] = line.slice(index + 1).trim();
  }
  return { host: remoteAddressArg, headers, st: headers.st, usn: headers.usn, location: headers.location };
};

const normalizeKeys = (recordArg: Record<string, unknown>): Record<string, unknown> => {
  const normalized: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(recordArg)) {
    normalized[key.toLowerCase()] = value;
  }
  return normalized;
};

const parseUrl = (valueArg: string | undefined): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `http://${valueArg}`;
  try {
    return new URL(withScheme);
  } catch {
    return undefined;
  }
};

const normalizeEndpointUrl = (valueArg: string | undefined): string | undefined => {
  const parsed = parseUrl(valueArg);
  if (!parsed) {
    return undefined;
  }
  return `${parsed.protocol}//${parsed.host}/`;
};

const urlPort = (urlArg: URL | undefined): number | undefined => urlArg?.port ? Number(urlArg.port) : undefined;
const normalizeUrn = (valueArg: string | undefined): string => (valueArg || '').toLowerCase().replace(/\.$/, '');

const isKnownManufacturer = (valueArg: string | undefined): boolean => {
  const value = (valueArg || '').toLowerCase();
  return huaweiLteSsdpManufacturers.some((manufacturerArg) => value.includes(manufacturerArg.toLowerCase())) || value.includes('huawei') || value.includes('soyea');
};

const stripUuid = (valueArg: string | undefined): string | undefined => valueArg?.replace(/^uuid:/i, '').trim() || undefined;
const hostForUrl = (hostArg: string): string => hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const booleanMetadata = (metadataArg: Record<string, unknown>, keyArg: string): boolean | undefined => typeof metadataArg[keyArg] === 'boolean' ? metadataArg[keyArg] as boolean : undefined;
const stringList = (valueArg: unknown): string[] | undefined => Array.isArray(valueArg)
  ? valueArg.map((itemArg) => stringValue(itemArg)).filter(Boolean) as string[]
  : stringValue(valueArg)?.split(',').map((itemArg) => itemArg.trim()).filter(Boolean);
const snapshotValue = (valueArg: unknown): IHuaweiLteSnapshot | undefined => valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'connection' in valueArg ? valueArg as IHuaweiLteSnapshot : undefined;
const rawDataValue = (valueArg: unknown): Partial<IHuaweiLteRawData> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Partial<IHuaweiLteRawData> : undefined;
