import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IBleboxManualEntry, IBleboxMdnsRecord } from './blebox.types.js';

const bleboxMdnsType = '_bbxsrv._tcp.local';
const knownProducts = new Set([
  'airsensor',
  'dimmerbox',
  'gatebox',
  'gatecontroller',
  'multisensor',
  'shutterbox',
  'switchbox',
  'switchboxd',
  'tempsensor',
  'wlightbox',
  'wlightboxs',
]);

export class BleboxMdnsMatcher implements IDiscoveryMatcher<IBleboxMdnsRecord> {
  public id = 'blebox-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize BleBox zeroconf records advertised as _bbxsrv._tcp.local.';

  public async matches(recordArg: IBleboxMdnsRecord): Promise<IDiscoveryMatch> {
    const type = normalizeMdnsType(recordArg.type);
    const name = recordArg.name?.toLowerCase() || '';
    const model = recordArg.txt?.type || recordArg.txt?.product || recordArg.txt?.model;
    const matched = type === bleboxMdnsType || name.includes('blebox') || isKnownProduct(model);
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'mDNS record is not a BleBox advertisement.',
      };
    }

    const id = recordArg.txt?.id || recordArg.txt?.mac || recordArg.name;
    return {
      matched: true,
      confidence: type === bleboxMdnsType ? 'certain' : 'high',
      reason: 'mDNS record matches BleBox zeroconf metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'blebox',
        id,
        host: recordArg.host,
        port: recordArg.port || 80,
        name: recordArg.txt?.deviceName || recordArg.name,
        manufacturer: 'BleBox',
        model,
        metadata: {
          mdnsName: recordArg.name,
          mdnsType: recordArg.type,
          txt: recordArg.txt,
        },
      },
    };
  }
}

export class BleboxManualMatcher implements IDiscoveryMatcher<IBleboxManualEntry> {
  public id = 'blebox-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual BleBox setup entries by host, manufacturer, model, or metadata.';

  public async matches(inputArg: IBleboxManualEntry): Promise<IDiscoveryMatch> {
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const model = inputArg.model?.toLowerCase() || '';
    const matched = Boolean(inputArg.host || manufacturer === 'blebox' || isKnownProduct(model) || inputArg.metadata?.blebox);
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'Manual entry does not contain BleBox setup hints.',
      };
    }
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start BleBox setup.',
      normalizedDeviceId: inputArg.id,
      candidate: {
        source: 'manual',
        integrationDomain: 'blebox',
        id: inputArg.id,
        host: inputArg.host,
        port: inputArg.port || 80,
        name: inputArg.name,
        manufacturer: 'BleBox',
        model: inputArg.model,
        metadata: inputArg.metadata,
      },
    };
  }
}

export class BleboxCandidateValidator implements IDiscoveryValidator {
  public id = 'blebox-candidate-validator';
  public description = 'Validate BleBox candidate metadata before starting local HTTP setup.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const model = candidateArg.model?.toLowerCase() || '';
    const matched = candidateArg.integrationDomain === 'blebox'
      || manufacturer === 'blebox'
      || isKnownProduct(model)
      || Boolean(candidateArg.metadata?.blebox);
    const hasUsableAddress = Boolean(candidateArg.host && (!candidateArg.port || isValidPort(candidateArg.port)));
    return {
      matched: matched && hasUsableAddress,
      confidence: matched && candidateArg.id ? 'certain' : matched && hasUsableAddress ? 'high' : matched ? 'medium' : 'low',
      reason: matched
        ? hasUsableAddress ? 'Candidate has BleBox metadata and a usable HTTP address.' : 'Candidate has BleBox metadata but no usable HTTP address.'
        : 'Candidate is not BleBox.',
      candidate: matched && hasUsableAddress ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createBleboxDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({
    integrationDomain: 'blebox',
    displayName: 'BleBox devices',
  })
    .addMatcher(new BleboxMdnsMatcher())
    .addMatcher(new BleboxManualMatcher())
    .addValidator(new BleboxCandidateValidator());
};

const normalizeMdnsType = (valueArg?: string): string => {
  return (valueArg || '').toLowerCase().replace(/\.$/, '');
};

const isKnownProduct = (valueArg?: string): boolean => {
  return Boolean(valueArg && knownProducts.has(valueArg.toLowerCase()));
};

const isValidPort = (valueArg: number): boolean => {
  return Number.isInteger(valueArg) && valueArg > 0 && valueArg <= 65535;
};
