import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { ddwrtDefaultHttpPort, ddwrtDefaultHttpsPort, ddwrtDefaultSsl, ddwrtDefaultVerifySsl, ddwrtDisplayName, ddwrtDomain, ddwrtManufacturer, ddwrtTextHints } from './ddwrt.constants.js';
import { DdwrtMapper } from './ddwrt.mapper.js';
import type { IDdwrtDhcpRecord, IDdwrtHttpStatusRecord, IDdwrtManualEntry, IDdwrtRawStatusSet, IDdwrtSnapshot, IDdwrtStatusData } from './ddwrt.types.js';

export class DdwrtDhcpMatcher implements IDiscoveryMatcher<IDdwrtDhcpRecord> {
  public id = 'ddwrt-dhcp-match';
  public source = 'dhcp' as const;
  public description = 'Recognize explicit DD-WRT DHCP records and DD-WRT hostname/metadata hints.';

  public async matches(recordArg: IDdwrtDhcpRecord): Promise<IDiscoveryMatch> {
    const metadata = recordArg.metadata || {};
    const host = recordArg.host || recordArg.ipAddress || recordArg.address || recordArg.ip || stringValue(metadata.host);
    const hostname = recordArg.hostname || recordArg.hostName || stringValue(metadata.hostname);
    const macAddress = DdwrtMapper.normalizeMac(recordArg.macAddress || recordArg.macaddress || recordArg.mac || stringValue(metadata.macAddress));
    const model = recordArg.model || stringValue(metadata.model);
    const registeredDevice = recordArg.registeredDevice === true || metadata.registeredDevice === true || metadata.registered_devices === true;
    const textMatched = ddwrtText(recordArg.integrationDomain, recordArg.manufacturer, model, hostname, metadata.brand, metadata.manufacturer).some((textArg) => ddwrtTextHints.some((hintArg) => textArg.includes(hintArg)));
    const matched = recordArg.integrationDomain === ddwrtDomain || metadata.ddwrt === true || textMatched;

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'DHCP record does not contain explicit DD-WRT metadata.' };
    }

    const ssl = booleanValue(metadata.ssl) ?? ddwrtDefaultSsl;
    const id = macAddress || hostname || host;
    return {
      matched: true,
      confidence: recordArg.integrationDomain === ddwrtDomain || metadata.ddwrt === true ? 'certain' : host ? 'high' : 'medium',
      reason: registeredDevice ? 'DHCP record carries DD-WRT registered-device metadata.' : 'DHCP record matches DD-WRT metadata or hostname hints.',
      normalizedDeviceId: id,
      candidate: {
        source: 'dhcp',
        integrationDomain: ddwrtDomain,
        id,
        host,
        port: ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort,
        name: hostname || ddwrtDisplayName,
        manufacturer: recordArg.manufacturer || ddwrtManufacturer,
        model: model || ddwrtDisplayName,
        macAddress,
        metadata: {
          ...metadata,
          ddwrt: true,
          discoveryProtocol: 'dhcp',
          hostname,
          registeredDevice,
          ssl,
          verifySsl: booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? ddwrtDefaultVerifySsl,
          nativeHttpImplemented: true,
        },
      },
      metadata: { registeredDevice, textMatched },
    };
  }
}

export class DdwrtHttpStatusMatcher implements IDiscoveryMatcher<IDdwrtHttpStatusRecord> {
  public id = 'ddwrt-http-status-match';
  public source = 'http' as const;
  public description = 'Recognize DD-WRT Status_*.live.asp response bodies or parsed status maps.';

  public async matches(recordArg: IDdwrtHttpStatusRecord): Promise<IDiscoveryMatch> {
    const metadata = recordArg.metadata || {};
    const host = recordArg.host || recordArg.ipAddress || recordArg.address || recordArg.ip || stringValue(metadata.host);
    const wirelessStatus = statusData(recordArg.wirelessStatus || recordArg.wirelessResponse || recordArg.body || recordArg.statusText || metadata.wirelessStatus || metadata.wirelessResponse);
    const lanStatus = statusData(recordArg.lanStatus || recordArg.lanResponse || metadata.lanStatus || metadata.lanResponse);
    const hasDdwrtData = Boolean(wirelessStatus?.active_wireless || lanStatus?.arp_table || lanStatus?.dhcp_leases || wirelessStatus?.dhcp_leases);
    const matched = recordArg.integrationDomain === ddwrtDomain || metadata.ddwrt === true || hasDdwrtData;
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'HTTP record is not a DD-WRT live status response.' };
    }

    const ssl = booleanValue(metadata.ssl) ?? ddwrtDefaultSsl;
    const snapshot = DdwrtMapper.toSnapshot({
      host,
      name: recordArg.name || stringValue(metadata.name),
      ssl,
      verifySsl: booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? ddwrtDefaultVerifySsl,
      rawStatus: { wireless: wirelessStatus, lan: lanStatus },
      wirelessResponse: recordArg.wirelessResponse || (recordArg.body && wirelessStatus ? recordArg.body : undefined),
      lanResponse: recordArg.lanResponse,
      metadata: { discoverySource: 'http-status' },
    }, { source: 'manual', connected: true });

    return {
      matched: true,
      confidence: hasDdwrtData ? 'certain' : host ? 'high' : 'medium',
      reason: hasDdwrtData ? 'HTTP response contains DD-WRT live status keys.' : 'HTTP record carries DD-WRT metadata.',
      normalizedDeviceId: snapshot.router.id,
      candidate: {
        source: 'http',
        integrationDomain: ddwrtDomain,
        id: snapshot.router.id,
        host,
        port: recordArg.port || (ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort),
        name: snapshot.router.name,
        manufacturer: ddwrtManufacturer,
        model: ddwrtDisplayName,
        metadata: {
          ...metadata,
          ddwrt: true,
          discoveryProtocol: 'http-status',
          snapshot,
          rawStatus: { wireless: wirelessStatus, lan: lanStatus } satisfies IDdwrtRawStatusSet,
          ssl,
          verifySsl: snapshot.router.verifySsl,
          nativeHttpImplemented: true,
        },
      },
    };
  }
}

export class DdwrtManualMatcher implements IDiscoveryMatcher<IDdwrtManualEntry> {
  public id = 'ddwrt-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual DD-WRT host, credentials, live status data, or snapshots.';

  public async matches(inputArg: IDdwrtManualEntry): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const snapshot = inputArg.snapshot || snapshotFromMetadata(metadata);
    const rawStatus = inputArg.rawStatus || rawStatusFromMetadata(metadata);
    const wirelessStatus = statusData(inputArg.wirelessStatus || inputArg.wirelessResponse || metadata.wirelessStatus || metadata.wirelessResponse);
    const lanStatus = statusData(inputArg.lanStatus || inputArg.lanResponse || metadata.lanStatus || metadata.lanResponse);
    const host = inputArg.host || inputArg.ipAddress || inputArg.address || inputArg.ip || snapshot?.router.host || stringValue(metadata.host);
    const macAddress = DdwrtMapper.normalizeMac(inputArg.macAddress || inputArg.mac || snapshot?.router.macAddress || stringValue(metadata.macAddress));
    const model = inputArg.model || snapshot?.router.model || stringValue(metadata.model);
    const hasStatus = Boolean(rawStatus || wirelessStatus || lanStatus || snapshot);
    const hasCredentials = Boolean(inputArg.username || inputArg.password || metadata.username || metadata.password);
    const textMatched = ddwrtText(inputArg.integrationDomain, inputArg.manufacturer, model, inputArg.name, metadata.brand, metadata.manufacturer).some((textArg) => ddwrtTextHints.some((hintArg) => textArg.includes(hintArg)));
    const matched = inputArg.integrationDomain === ddwrtDomain || metadata.ddwrt === true || hasStatus || textMatched || Boolean(host && hasCredentials);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain DD-WRT setup data.' };
    }

    const ssl = inputArg.ssl ?? inputArg.useSsl ?? booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? ddwrtDefaultSsl;
    const id = inputArg.id || macAddress || snapshot?.router.id || host;
    return {
      matched: true,
      confidence: snapshot || rawStatus || wirelessStatus || lanStatus ? 'certain' : host && hasCredentials ? 'high' : host ? 'medium' : 'low',
      reason: snapshot ? 'Manual entry includes a DD-WRT snapshot.' : hasStatus ? 'Manual entry includes DD-WRT live status data.' : 'Manual entry can start DD-WRT setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: ddwrtDomain,
        id,
        host,
        port: inputArg.port || snapshot?.router.port || (ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort),
        name: inputArg.name || snapshot?.router.name || host || ddwrtDisplayName,
        manufacturer: inputArg.manufacturer || snapshot?.router.manufacturer || ddwrtManufacturer,
        model: model || ddwrtDisplayName,
        macAddress,
        metadata: {
          ...metadata,
          ddwrt: true,
          discoveryProtocol: 'manual',
          username: inputArg.username || stringValue(metadata.username),
          ssl,
          verifySsl: inputArg.verifySsl ?? inputArg.verify_ssl ?? booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.router.verifySsl ?? ddwrtDefaultVerifySsl,
          wirelessOnly: inputArg.wirelessOnly ?? inputArg.wireless_only ?? booleanValue(metadata.wirelessOnly ?? metadata.wireless_only),
          snapshot,
          rawStatus: rawStatus || { wireless: wirelessStatus, lan: lanStatus },
          nativeHttpImplemented: true,
        },
      },
      metadata: { snapshotConfigured: Boolean(snapshot), rawStatusConfigured: hasStatus, credentialsConfigured: hasCredentials },
    };
  }
}

export class DdwrtCandidateValidator implements IDiscoveryValidator {
  public id = 'ddwrt-candidate-validator';
  public description = 'Validate DD-WRT candidates have a host, snapshot, or live status data.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const snapshot = snapshotFromMetadata(metadata);
    const rawStatus = rawStatusFromMetadata(metadata);
    const textMatched = ddwrtText(candidateArg.integrationDomain, candidateArg.name, candidateArg.manufacturer, candidateArg.model, metadata.brand, metadata.manufacturer).some((textArg) => ddwrtTextHints.some((hintArg) => textArg.includes(hintArg)));
    const matched = candidateArg.integrationDomain === ddwrtDomain || metadata.ddwrt === true || Boolean(snapshot || rawStatus) || textMatched;
    const usable = Boolean(candidateArg.host || snapshot || rawStatus);
    const normalizedDeviceId = candidateArg.id || candidateArg.macAddress || snapshot?.router.id || candidateArg.host;

    if (!matched || !usable) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'DD-WRT candidate lacks host, snapshot, or live status data.' : 'Candidate is not DD-WRT.',
        normalizedDeviceId,
      };
    }

    const ssl = booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? ddwrtDefaultSsl;
    return {
      matched: true,
      confidence: snapshot || rawStatus || candidateArg.macAddress ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has DD-WRT metadata and a usable local source.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        integrationDomain: ddwrtDomain,
        port: candidateArg.port || (ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort),
        manufacturer: candidateArg.manufacturer || ddwrtManufacturer,
        metadata: {
          ...metadata,
          ddwrt: true,
          ssl,
          verifySsl: booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.router.verifySsl ?? ddwrtDefaultVerifySsl,
          nativeHttpImplemented: true,
        },
      },
      metadata: { snapshotConfigured: Boolean(snapshot), rawStatusConfigured: Boolean(rawStatus), ssl },
    };
  }
}

export const createDdwrtDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: ddwrtDomain, displayName: ddwrtDisplayName })
    .addMatcher(new DdwrtDhcpMatcher())
    .addMatcher(new DdwrtHttpStatusMatcher())
    .addMatcher(new DdwrtManualMatcher())
    .addValidator(new DdwrtCandidateValidator());
};

const statusData = (valueArg: unknown): IDdwrtStatusData | undefined => DdwrtMapper.toStatusData(valueArg);

const snapshotFromMetadata = (metadataArg: Record<string, unknown>): IDdwrtSnapshot | undefined => {
  const snapshot = metadataArg.snapshot;
  return snapshot && typeof snapshot === 'object' && 'router' in snapshot && 'clients' in snapshot && 'sensors' in snapshot ? snapshot as IDdwrtSnapshot : undefined;
};

const rawStatusFromMetadata = (metadataArg: Record<string, unknown>): IDdwrtRawStatusSet | undefined => {
  const rawStatus = metadataArg.rawStatus;
  return rawStatus && typeof rawStatus === 'object' && !Array.isArray(rawStatus) ? rawStatus as IDdwrtRawStatusSet : undefined;
};

const ddwrtText = (...valuesArg: unknown[]): string[] => [valuesArg.filter((valueArg): valueArg is string => typeof valueArg === 'string').join(' ').toLowerCase()];

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

const booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'string') {
    const normalized = valueArg.trim().toLowerCase();
    if (['true', 'yes', '1', 'on'].includes(normalized)) {
      return true;
    }
    if (['false', 'no', '0', 'off'].includes(normalized)) {
      return false;
    }
  }
  return undefined;
};
