import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { MikrotikMapper } from './mikrotik.mapper.js';
import type { IMikrotikManualDiscoveryRecord, IMikrotikSnapshot } from './mikrotik.types.js';
import { mikrotikDefaultApiPort, mikrotikDomain, mikrotikManufacturer } from './mikrotik.types.js';

const mikrotikTextHints = ['mikrotik', 'routeros', 'routerboard', 'router os', 'crs', 'ccr', 'hex', 'hap', 'cap ac', 'cap ax'];

export class MikrotikManualMatcher implements IDiscoveryMatcher<IMikrotikManualDiscoveryRecord> {
  public id = 'mikrotik-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Mikrotik RouterOS/API setup entries, including snapshot-only records.';

  public async matches(inputArg: IMikrotikManualDiscoveryRecord): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const snapshot = inputArg.snapshot || metadata.snapshot as IMikrotikSnapshot | undefined;
    const host = inputArg.host || snapshot?.router.host;
    const verifySsl = this.booleanValue(inputArg.verifySsl) ?? this.booleanValue(metadata.verifySsl) ?? snapshot?.router.verifySsl ?? false;
    const mac = MikrotikMapper.normalizeMac(inputArg.macAddress || snapshot?.router.macAddress);
    const text = this.text(inputArg.integrationDomain, inputArg.manufacturer, inputArg.model, inputArg.name, metadata.manufacturer, metadata.model, metadata.name, metadata.protocol, snapshot?.router.manufacturer, snapshot?.router.model, snapshot?.router.boardName, snapshot?.router.name);
    const hasSnapshot = Boolean(snapshot);
    const matched = inputArg.integrationDomain === mikrotikDomain
      || metadata.mikrotik === true
      || metadata.routeros === true
      || metadata.routerOs === true
      || hasSnapshot
      || mikrotikTextHints.some((hintArg) => text.includes(hintArg))
      || Boolean(host && !text);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Mikrotik RouterOS/API setup hints.' };
    }

    const port = inputArg.port || snapshot?.router.port || mikrotikDefaultApiPort;
    const id = inputArg.id || inputArg.serialNumber || snapshot?.router.serialNumber || mac || snapshot?.router.id || (host ? `${host}:${port}` : undefined);
    return {
      matched: true,
      confidence: hasSnapshot || mac || inputArg.serialNumber ? 'certain' : host ? 'high' : 'medium',
      reason: hasSnapshot ? 'Manual entry includes a Mikrotik snapshot.' : 'Manual entry can start Mikrotik RouterOS/API setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: mikrotikDomain,
        id,
        host,
        port,
        name: inputArg.name || snapshot?.router.name || snapshot?.router.identity || host || 'Mikrotik',
        manufacturer: inputArg.manufacturer || snapshot?.router.manufacturer || mikrotikManufacturer,
        model: inputArg.model || snapshot?.router.model || snapshot?.router.boardName || 'RouterOS device',
        serialNumber: inputArg.serialNumber || snapshot?.router.serialNumber,
        macAddress: mac,
        metadata: {
          ...metadata,
          mikrotik: true,
          routeros: true,
          protocol: MikrotikMapper.protocol(verifySsl),
          verifySsl,
          hasSnapshot,
          upstreamPlatforms: ['device_tracker'],
          liveRouterOsApiImplemented: false,
        },
      },
      metadata: { hasSnapshot, verifySsl, protocol: MikrotikMapper.protocol(verifySsl), liveRouterOsApiImplemented: false },
    };
  }

  private text(...valuesArg: unknown[]): string {
    return valuesArg.filter((valueArg): valueArg is string => typeof valueArg === 'string').join(' ').toLowerCase();
  }

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

export class MikrotikCandidateValidator implements IDiscoveryValidator {
  public id = 'mikrotik-candidate-validator';
  public description = 'Validate Mikrotik candidates have a host or snapshot and RouterOS identity metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as IMikrotikSnapshot | undefined;
    const mac = MikrotikMapper.normalizeMac(candidateArg.macAddress || snapshot?.router.macAddress);
    const text = [candidateArg.integrationDomain, candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.manufacturer, metadata.model, metadata.name, metadata.protocol, snapshot?.router.manufacturer, snapshot?.router.model, snapshot?.router.boardName]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const matched = candidateArg.integrationDomain === mikrotikDomain
      || metadata.mikrotik === true
      || metadata.routeros === true
      || metadata.routerOs === true
      || Boolean(snapshot)
      || mikrotikTextHints.some((hintArg) => text.includes(hintArg));
    const hasUsableSource = Boolean(candidateArg.host || snapshot);

    if (!matched || !hasUsableSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'Mikrotik candidate lacks host or snapshot information.' : 'Candidate is not Mikrotik RouterOS/API.',
      };
    }

    const verifySsl = this.booleanValue(metadata.verifySsl) ?? snapshot?.router.verifySsl ?? false;
    const port = candidateArg.port || snapshot?.router.port || mikrotikDefaultApiPort;
    const normalizedDeviceId = candidateArg.id || snapshot?.router.serialNumber || mac || (candidateArg.host ? `${candidateArg.host}:${port}` : snapshot?.router.id);
    return {
      matched: true,
      confidence: mac || snapshot ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has Mikrotik metadata and a usable local RouterOS/API source.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        id: candidateArg.id || normalizedDeviceId,
        port,
        macAddress: mac || candidateArg.macAddress,
        metadata: {
          ...metadata,
          mikrotik: true,
          routeros: true,
          protocol: MikrotikMapper.protocol(verifySsl),
          verifySsl,
          upstreamPlatforms: ['device_tracker'],
          liveRouterOsApiImplemented: false,
        },
      },
      metadata: { verifySsl, protocol: MikrotikMapper.protocol(verifySsl), liveRouterOsApiImplemented: false },
    };
  }

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

export const createMikrotikDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: mikrotikDomain, displayName: 'Mikrotik' })
    .addMatcher(new MikrotikManualMatcher())
    .addValidator(new MikrotikCandidateValidator());
};
