import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IIppAttributeRecord, IIppManualEntry, IIppMdnsRecord, IIppSnapshot } from './ipp.types.js';
import { ippDefaultBasePath, ippDefaultPort } from './ipp.types.js';

const ippMdnsTypes = ['_ipp._tcp.local', '_ipps._tcp.local'];

export class IppMdnsMatcher implements IDiscoveryMatcher<IIppMdnsRecord> {
  public id = 'ipp-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize IPP and IPPS printer mDNS advertisements.';

  public async matches(recordArg: IIppMdnsRecord): Promise<IDiscoveryMatch> {
    const type = normalizeType(recordArg.type);
    const properties = { ...recordArg.txt, ...recordArg.properties };
    const matched = ippMdnsTypes.includes(type);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not an IPP printer advertisement.' };
    }

    const tls = type === '_ipps._tcp.local';
    const uuid = valueForKey(properties, 'UUID') || valueForKey(properties, 'uuid');
    const rp = valueForKey(properties, 'rp') || ippDefaultBasePath;
    const name = cleanName(recordArg.name || recordArg.hostname, type);
    return {
      matched: true,
      confidence: uuid ? 'certain' : 'high',
      reason: 'mDNS record matches IPP printer service metadata.',
      normalizedDeviceId: uuid || recordArg.host || recordArg.addresses?.[0],
      candidate: {
        source: 'mdns',
        integrationDomain: 'ipp',
        id: uuid,
        host: recordArg.host || recordArg.addresses?.[0],
        port: recordArg.port || ippDefaultPort,
        name: name || valueForKey(properties, 'ty'),
        manufacturer: valueForKey(properties, 'usb_MFG'),
        model: valueForKey(properties, 'usb_MDL') || valueForKey(properties, 'ty') || valueForKey(properties, 'product'),
        metadata: {
          ipp: true,
          mdnsName: recordArg.name,
          mdnsType: recordArg.type,
          txt: properties,
          tls,
          verifySsl: false,
          basePath: normalizeBasePath(rp),
          uuid,
          rp,
        },
      },
    };
  }
}

export class IppManualMatcher implements IDiscoveryMatcher<IIppManualEntry> {
  public id = 'ipp-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual IPP printer setup entries.';

  public async matches(inputArg: IIppManualEntry): Promise<IDiscoveryMatch> {
    const parsed = parseHost(inputArg.host);
    const metadata = inputArg.metadata || {};
    const snapshot = inputArg.snapshot || snapshotFromMetadata(metadata);
    const attributes = inputArg.attributes || attributesFromMetadata(metadata);
    const haystack = `${inputArg.name || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
    const matched = Boolean(inputArg.host || inputArg.port === ippDefaultPort || inputArg.basePath || metadata.ipp || snapshot || attributes || haystack.includes('ipp') || haystack.includes('printer'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain IPP setup hints.' };
    }

    const port = inputArg.port || parsed?.port || ippDefaultPort;
    const tls = inputArg.tls ?? inputArg.ssl ?? parsed?.tls ?? false;
    const basePath = normalizeBasePath(inputArg.basePath || parsed?.basePath || stringMetadata(metadata, 'basePath') || ippDefaultBasePath);
    const id = inputArg.id || inputArg.uuid || inputArg.serialNumber || snapshot?.printer.uuid || snapshot?.printer.serialNumber || (inputArg.host ? `${parsed?.host || inputArg.host}:${port}${basePath}` : undefined);
    return {
      matched: true,
      confidence: inputArg.host || snapshot || attributes ? 'high' : 'medium',
      reason: 'Manual entry can start IPP printer setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: 'ipp',
        id,
        host: parsed?.host || inputArg.host,
        port,
        name: inputArg.name || snapshot?.printer.name,
        manufacturer: inputArg.manufacturer || snapshot?.printer.manufacturer,
        model: inputArg.model || snapshot?.printer.model,
        serialNumber: inputArg.serialNumber || snapshot?.printer.serialNumber,
        metadata: {
          ...metadata,
          ipp: true,
          tls,
          verifySsl: inputArg.verifySsl ?? booleanMetadata(metadata, 'verifySsl') ?? false,
          basePath,
          uuid: inputArg.uuid || snapshot?.printer.uuid,
          snapshot,
          attributes,
        },
      },
    };
  }
}

export class IppCandidateValidator implements IDiscoveryValidator {
  public id = 'ipp-candidate-validator';
  public description = 'Validate IPP candidates have printer metadata and a usable host, snapshot, attributes, or client.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const type = normalizeType(stringMetadata(metadata, 'mdnsType'));
    const haystack = `${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''}`.toLowerCase();
    const matched = candidateArg.integrationDomain === 'ipp'
      || candidateArg.port === ippDefaultPort
      || Boolean(metadata.ipp)
      || ippMdnsTypes.includes(type)
      || haystack.includes('ipp')
      || haystack.includes('printer');
    const hasUsableSource = Boolean(candidateArg.host || metadata.snapshot || metadata.attributes || metadata.client);

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

    const basePath = normalizeBasePath(stringMetadata(metadata, 'basePath') || ippDefaultBasePath);
    const normalizedDeviceId = candidateArg.id || stringMetadata(metadata, 'uuid') || candidateArg.serialNumber || (candidateArg.host ? `${candidateArg.host}:${candidateArg.port || ippDefaultPort}${basePath}` : undefined);
    return {
      matched: true,
      confidence: candidateArg.id || stringMetadata(metadata, 'uuid') || candidateArg.serialNumber ? 'certain' : 'high',
      reason: 'Candidate has IPP metadata and a usable source.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        integrationDomain: 'ipp',
        port: candidateArg.port || ippDefaultPort,
        metadata: {
          ...metadata,
          ipp: true,
          basePath,
          tls: booleanMetadata(metadata, 'tls') ?? false,
          verifySsl: booleanMetadata(metadata, 'verifySsl') ?? false,
        },
      },
    };
  }
}

export const createIppDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'ipp', displayName: 'Internet Printing Protocol (IPP)' })
    .addMatcher(new IppMdnsMatcher())
    .addMatcher(new IppManualMatcher())
    .addValidator(new IppCandidateValidator());
};

const normalizeType = (valueArg?: string): string => (valueArg || '').toLowerCase().replace(/\.$/, '');

const valueForKey = (recordArg: Record<string, string | undefined> | undefined, keyArg: string): string | undefined => {
  if (!recordArg) {
    return undefined;
  }
  const lowerKey = keyArg.toLowerCase();
  for (const [key, value] of Object.entries(recordArg)) {
    if (key.toLowerCase() === lowerKey) {
      return value;
    }
  }
  return undefined;
};

const cleanName = (valueArg: string | undefined, typeArg: string): string => {
  const escaped = typeArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  return valueArg?.replace(new RegExp(`\\.${escaped}\\.?$`, 'i'), '').replace(/\.local\.?$/i, '').trim() || '';
};

const normalizeBasePath = (valueArg: string | undefined): string => {
  const value = (valueArg || ippDefaultBasePath).trim() || ippDefaultBasePath;
  return value.startsWith('/') ? value : `/${value}`;
};

const parseHost = (valueArg: string | undefined): { host: string; port?: number; basePath?: string; tls?: boolean } | undefined => {
  if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    const url = new URL(valueArg.replace(/^ipps:/i, 'https:').replace(/^ipp:/i, 'http:'));
    return {
      host: url.hostname,
      port: url.port ? Number(url.port) : undefined,
      basePath: url.pathname && url.pathname !== '/' ? url.pathname : undefined,
      tls: url.protocol === 'https:',
    };
  } catch {
    return undefined;
  }
};

const stringMetadata = (metadataArg: Record<string, unknown>, keyArg: string): string | undefined => {
  const value = metadataArg[keyArg];
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
};

const booleanMetadata = (metadataArg: Record<string, unknown>, keyArg: string): boolean | undefined => {
  const value = metadataArg[keyArg];
  return typeof value === 'boolean' ? value : undefined;
};

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

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