import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { dsmrVersions } from './dsmr.constants.js';
import type { IDsmrManualEntry, TDsmrConnectionType } from './dsmr.types.js';
import { dsmrDefaultNetworkPort, dsmrDomain } from './dsmr.types.js';

export class DsmrManualMatcher implements IDiscoveryMatcher<IDsmrManualEntry> {
  public id = 'dsmr-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual DSMR P1 serial and network setup entries without probing live hardware.';

  public async matches(inputArg: IDsmrManualEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const connectionType = this.connectionType(inputArg);
    const serialPort = this.serialPort(inputArg);
    const host = this.stringValue(inputArg.host);
    const hasEndpoint = connectionType === 'network' ? Boolean(host) : Boolean(serialPort);
    const hasHint = this.hasDsmrHint(inputArg);
    if (!hasEndpoint || !hasHint) {
      return {
        matched: false,
        confidence: 'low',
        reason: hasEndpoint ? 'Manual entry does not contain a DSMR/P1 hint.' : 'Manual entry does not contain a DSMR serial path or network host.',
      };
    }
    const id = this.normalizedId(inputArg, connectionType, serialPort);
    return {
      matched: true,
      confidence: inputArg.metadata?.dsmr || inputArg.metadata?.p1 || inputArg.protocol === dsmrDomain ? 'high' : 'medium',
      reason: `Manual entry can be configured as a DSMR ${connectionType} meter.`,
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: dsmrDomain,
        id,
        host: connectionType === 'network' ? host : undefined,
        port: connectionType === 'network' ? this.numberValue(inputArg.port) || dsmrDefaultNetworkPort : undefined,
        name: inputArg.name || 'DSMR Smart Meter',
        manufacturer: inputArg.manufacturer || 'DSMR',
        model: inputArg.model || 'P1 smart meter',
        serialNumber: inputArg.serialNumber,
        metadata: {
          ...inputArg.metadata,
          connectionType,
          serialPort: connectionType === 'serial' ? serialPort : undefined,
          protocol: this.protocol(inputArg.protocol || inputArg.metadata?.protocol),
          dsmrVersion: this.dsmrVersion(inputArg.dsmrVersion || inputArg.metadata?.dsmrVersion || inputArg.metadata?.dsmr_version),
          liveValidation: false,
        },
      },
    };
  }

  private connectionType(inputArg: IDsmrManualEntry): TDsmrConnectionType {
    const value = String(inputArg.type || inputArg.metadata?.connectionType || '').toLowerCase();
    if (value === 'network' || inputArg.host) {
      return 'network';
    }
    return 'serial';
  }

  private hasDsmrHint(inputArg: IDsmrManualEntry): boolean {
    const haystack = `${inputArg.name || ''} ${inputArg.protocol || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
    return inputArg.metadata?.dsmr === true
      || inputArg.metadata?.p1 === true
      || inputArg.metadata?.smartMeter === true
      || inputArg.protocol === dsmrDomain
      || haystack.includes('dsmr')
      || haystack.includes('p1')
      || haystack.includes('smart meter')
      || haystack.includes('slimme meter');
  }

  private normalizedId(inputArg: IDsmrManualEntry, connectionTypeArg: TDsmrConnectionType, serialPortArg: string | undefined): string {
    const endpoint = connectionTypeArg === 'network'
      ? `${inputArg.host || 'dsmr'}_${this.numberValue(inputArg.port) || dsmrDefaultNetworkPort}`
      : serialPortArg || inputArg.serialNumber || 'dsmr_serial';
    return slug(String(inputArg.id || inputArg.serialNumber || endpoint));
  }

  private serialPort(inputArg: IDsmrManualEntry): string | undefined {
    const value = inputArg.serialPort || inputArg.device || inputArg.path || (typeof inputArg.port === 'string' && !Number.isFinite(Number(inputArg.port)) ? inputArg.port : undefined);
    return this.stringValue(value);
  }

  private protocol(valueArg: unknown): string {
    return valueArg === 'rfxtrx_dsmr_protocol' ? 'rfxtrx_dsmr_protocol' : 'dsmr_protocol';
  }

  private dsmrVersion(valueArg: unknown): string {
    return typeof valueArg === 'string' && dsmrVersions.includes(valueArg as never) ? valueArg : '2.2';
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private numberValue(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
  }
}

export class DsmrCandidateValidator implements IDiscoveryValidator {
  public id = 'dsmr-candidate-validator';
  public description = 'Validate DSMR candidates have explicit serial or network setup data without claiming live connectivity.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const connectionType = String(candidateArg.metadata?.connectionType || (candidateArg.host ? 'network' : 'serial')).toLowerCase() as TDsmrConnectionType;
    const serialPort = typeof candidateArg.metadata?.serialPort === 'string' ? candidateArg.metadata.serialPort : undefined;
    const matched = candidateArg.integrationDomain === dsmrDomain && (connectionType === 'network' ? Boolean(candidateArg.host) : Boolean(serialPort));
    const id = slug(candidateArg.id || candidateArg.serialNumber || (connectionType === 'network' ? `${candidateArg.host || 'dsmr'}_${candidateArg.port || dsmrDefaultNetworkPort}` : serialPort || 'dsmr_serial'));
    return {
      matched,
      confidence: matched ? 'high' : 'low',
      reason: matched ? 'Candidate has explicit DSMR setup data; live communication is not assumed.' : 'Candidate is missing DSMR serial or network setup data.',
      normalizedDeviceId: matched ? id : undefined,
      candidate: matched ? {
        ...candidateArg,
        id,
        integrationDomain: dsmrDomain,
        port: connectionType === 'network' ? candidateArg.port || dsmrDefaultNetworkPort : candidateArg.port,
        manufacturer: candidateArg.manufacturer || 'DSMR',
        model: candidateArg.model || 'P1 smart meter',
        metadata: {
          ...candidateArg.metadata,
          connectionType,
          liveValidation: false,
        },
      } : undefined,
    };
  }
}

export const createDsmrDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: dsmrDomain, displayName: 'DSMR Smart Meter' })
    .addMatcher(new DsmrManualMatcher())
    .addValidator(new DsmrCandidateValidator());
};

const slug = (valueArg: string): string => valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || dsmrDomain;
